權限#

權限可用於控制 Node.js 程序可以存取哪些系統資源,或程序可以對這些資源執行哪些動作。權限也可以控制其他模組可以存取哪些模組。

  • 基於模組的權限控制在應用程式執行期間其他模組可以使用哪些檔案或 URL。例如,這可用於控制哪些模組可以被第三方相依性存取。

  • 基於程序的權限控制 Node.js 程序對資源的存取。資源可以完全允許或拒絕,或者可以控制與之相關的動作。例如,可以允許檔案系統讀取,同時拒絕寫入。

如果您發現潛在的安全漏洞,請參閱我們的安全政策

基於模組的權限#

政策#

穩定性:1 - 實驗性

Node.js 包含用於在載入程式碼時建立政策的實驗性支援。

政策是一項安全功能,旨在確保載入程式碼的完整性。

雖然它無法作為追蹤程式碼來源的來源機制,但它可作為防範惡意程式碼執行的強固防禦措施。與可能在載入程式碼後限制功能的基於執行時期的模型不同,Node.js 政策著重於從一開始就防止惡意程式碼完全載入應用程式。

使用政策假設政策檔案有安全措施,例如使用檔案權限確保政策檔案不會被 Node.js 應用程式覆寫。

最佳實務做法是確保政策清單對正在執行的 Node.js 應用程式為唯讀,且檔案無法被正在執行的 Node.js 應用程式以任何方式變更。典型的設定會將政策檔案建立為與執行 Node.js 不同的使用者 ID,並授予正在執行 Node.js 的使用者 ID 讀取權限。

啟用#

載入模組時,可以使用 --experimental-policy 旗標啟用政策功能。

設定此旗標後,所有模組都必須符合傳遞給旗標的政策清單檔案

node --experimental-policy=policy.json app.js 

政策清單將用於強制執行 Node.js 載入程式碼的限制。

為減輕對磁碟上政策檔案的竄改,可透過 --policy-integrity 提供政策檔案本身的完整性。這允許執行 node 並聲明政策檔案內容,即使檔案在磁碟上已變更。

node --experimental-policy=policy.json --policy-integrity="sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0" app.js 
功能#
錯誤行為#

當政策檢查失敗時,Node.js 預設會擲回錯誤。可以透過在政策清單中定義「onerror」欄位,將錯誤行為變更為數種可能性之一。下列值可用於變更行為

  • "exit":會立即結束程序。不允許執行任何清理程式碼。
  • "log":會在失敗處記錄錯誤。
  • "throw":會在失敗處擲回 JS 錯誤。這是預設值。
{
  "onerror": "log",
  "resources": {
    "./app/checked.js": {
      "integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
    }
  }
} 
完整性檢查#

政策檔案必須使用完整性檢查,並使用與瀏覽器 完整性屬性 相容的子資源完整性字串,該屬性與絕對 URL 相關聯。

使用 require()import 時,如果已指定政策清單,則會檢查載入中涉及的所有資源的完整性。如果資源與清單中列出的完整性不符,則會擲回錯誤。

允許載入檔案 checked.js 的範例政策檔案

{
  "resources": {
    "./app/checked.js": {
      "integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
    }
  }
} 

政策清單中列出的每個資源都可以採用下列格式之一來確定其位置

  1. 從清單中到資源的 相對 URL 字串,例如 ./resource.js../resource.js/resource.js
  2. 到資源的完整 URL 字串,例如 file:///resource.js

載入資源時,必須完全符合 URL,包括搜尋參數和雜湊片段。嘗試載入 ./a.js 時不會使用 ./a.js?b,反之亦然。

若要產生完整性字串,可以使用類似 node -e 'process.stdout.write("sha256-");process.stdin.pipe(crypto.createHash("sha256").setEncoding("base64")).pipe(process.stdout)' < FILE 的指令碼。

完整性可以指定為布林值 true,以接受資源的任何主體,這對於本地開發很有用。不建議在生產中使用,因為它允許將資源的意外變更視為有效。

相依項重新導向#

應用程式可能需要發布模組的修補版本,或防止模組允許所有模組存取所有其他模組。重新導向可以用於攔截載入想要替換的模組的嘗試。

{
  "resources": {
    "./app/checked.js": {
      "dependencies": {
        "fs": true,
        "os": "./app/node_modules/alt-os",
        "http": { "import": true }
      }
    }
  }
} 

相依項以請求的規格字串為鍵,並具有 truenull、指向要解析的模組的字串或條件物件的值。

規格字串不執行任何搜尋,且必須與提供給 require()import 的內容完全相符,但正規化步驟除外。因此,如果政策使用多個不同的字串指向同一個模組(例如排除副檔名),則可能需要在政策中使用多個規格字串。

規格字串會正規化,但在用於比對之前不會解析,以便與匯入地圖保持一些相容性,例如,如果資源 file:///C:/app/utils.js 從位於 file:///C:/app/policy.json 的政策獲得以下重新導向

{
  "resources": {
    "file:///C:/app/utils.js": {
      "dependencies": {
        "./utils.js": "./utils-v2.js"
      }
    }
  }
} 

任何用於載入 file:///C:/app/utils.js 的規格字串都將被攔截,並重新導向到 file:///C:/app/utils-v2.js,而不管使用絕對或相對規格字串。但是,如果使用不是絕對或相對 URL 字串的規格字串,則不會攔截。因此,如果使用 import('#utils') 等匯入,則不會攔截。

如果重新導向的值為 true,則會使用政策檔案頂端的「相依性」欄位。如果政策檔案頂端的欄位為 true,則會使用預設節點搜尋演算法來尋找模組。

如果重新導向的值為字串,則會相對於明細檔解析,然後立即使用,而不會搜尋。

任何嘗試解析但未列在相依性中的規格字串,都會根據政策產生錯誤。

可以指定相依性對應的布林值 true,以允許模組載入任何規格,而不會重新導向。這對於在地端開發很有用,在製作過程中也可能有某些有效的用法,但僅在稽核模組以確保其行為有效後,才應審慎使用。

類似於 package.json 中的 "exports",相依性也可以指定為包含條件的物件,這些條件會分支相依性載入的方式。在前面的範例中,當 "import" 條件是載入的一部分時,會允許 "http"

解析值的 null 值會導致解析失敗。這可以用來確保明確禁止某些類型的動態存取。

解析模組位置的未知值會導致失敗,但無法保證向前相容。

政策重新導向的所有保證都指定在 保證 區段中。

範例:修補的相依性#

重新導向的相依性可以提供符合應用程式的減弱或修改功能。例如,透過包裝原始碼來記錄功能持續時間的記錄資料

const original = require('fn');
module.exports = function fn(...args) {
  console.time();
  try {
    return new.target ?
      Reflect.construct(original, args) :
      Reflect.apply(original, this, args);
  } finally {
    console.timeEnd();
  }
}; 
範圍#

使用宣告檔的 "scopes" 欄位,一次設定多個資源的組態。"scopes" 欄位透過比對資源的片段來運作。如果範圍或資源包含 "cascade": true,會在包含範圍中搜尋未知的規格說明。遞迴減少資源 URL 的方式,找出串接的包含範圍,方法是移除 特殊方案 的片段,保留尾端的 "/" 字尾,並移除查詢和雜湊片段。這會讓 URL 最終減為其來源。如果 URL 不是特殊網址,範圍會透過 URL 的來源來找到。如果找不到來源的範圍,或在不透明來源的情況下,可以使用協定字串作為範圍。如果找不到 URL 協定的範圍,會使用最後的空字串 "" 範圍。

請注意,blob: URL 會從包含的路徑採用其來源,因此範圍為 "blob:https://node.dev.org.tw" 沒有作用,因為沒有 URL 的來源可以是 blob:https://node.dev.org.tw;以 blob:https://node.dev.org.tw/ 開頭的 URL 會使用 https://node.dev.org.tw 作為其來源,因此會使用 https: 作為其協定範圍。對於不透明來源 blob: URL,它們會使用 blob: 作為其協定範圍,因為它們不會採用來源。

範例#
{
  "scopes": {
    "file:///C:/app/": {},
    "file:": {},
    "": {}
  }
} 

假設有一個檔案位於 file:///C:/app/bin/main.js,會按順序檢查以下範圍

  1. "file:///C:/app/bin/"

這會決定 "file:///C:/app/bin/" 中所有基於檔案的資源的政策。這不在政策的 "scopes" 欄位中,會被略過。將此範圍新增到政策中,會讓它在 "file:///C:/app/" 範圍之前使用。

  1. "file:///C:/app/"

這會決定 "file:///C:/app/" 內所有基於檔案的資源的政策。這在政策的 "scopes" 欄位中,且會決定 file:///C:/app/bin/main.js 資源的政策。如果範圍有 "cascade": true,任何關於資源未滿足的查詢會委派給 file:///C:/app/bin/main.js 的下一個相關範圍 "file:"

  1. "file:///C:/"

這會決定 "file:///C:/" 內所有基於檔案的資源的政策。這不在政策的 "scopes" 欄位中,且會被略過。它不會用於 file:///C:/app/bin/main.js,除非 "file:///C:/app/" 設為串接或不在政策的 "scopes" 中。

  1. "file:///"

這會決定 localhost 上所有基於檔案的資源的政策。這不在政策的 "scopes" 欄位中,且會被略過。它不會用於 file:///C:/app/bin/main.js,除非 "file:///C:/" 設為串接或不在政策的 "scopes" 中。

  1. "file:"

這會決定所有基於檔案的資源的政策。它不會用於 file:///C:/app/bin/main.js,除非 "file:///" 設為串接或不在政策的 "scopes" 中。

  1. ""

這會決定所有資源的政策。它不會用於 file:///C:/app/bin/main.js,除非 "file:" 設為串接。

使用範圍的完整性#

在範圍上將完整性設為 true 會將清單中找不到的任何資源的完整性設為 true

在範圍上將完整性設為 null 會將清單中找不到的任何資源的完整性設為不匹配。

不包含完整性等同於將完整性設定為 null

如果明確設定 "integrity",則完整性檢查的 "cascade" 會被忽略。

以下範例允許載入任何檔案

{
  "scopes": {
    "file:": {
      "integrity": true
    }
  }
} 
使用範圍的依存關係重新導向#

以下範例允許存取 ./app/ 內所有資源的 fs

{
  "resources": {
    "./app/checked.js": {
      "cascade": true,
      "integrity": true
    }
  },
  "scopes": {
    "./app/": {
      "dependencies": {
        "fs": true
      }
    }
  }
} 

以下範例允許存取所有 data: 資源的 fs

{
  "resources": {
    "data:text/javascript,import('node:fs');": {
      "cascade": true,
      "integrity": true
    }
  },
  "scopes": {
    "data:": {
      "dependencies": {
        "fs": true
      }
    }
  }
} 
範例:模擬匯入地圖#

給定匯入地圖

{
  "imports": {
    "react": "./app/node_modules/react/index.js"
  },
  "scopes": {
    "./ssr/": {
      "react": "./app/node_modules/server-side-react/index.js"
    }
  }
} 
{
  "dependencies": true,
  "scopes": {
    "": {
      "cascade": true,
      "dependencies": {
        "react": "./app/node_modules/react/index.js"
      }
    },
    "./ssr/": {
      "cascade": true,
      "dependencies": {
        "react": "./app/node_modules/server-side-react/index.js"
      }
    }
  }
} 

匯入地圖假設您預設可以取得任何資源。這表示政策最上層的 "dependencies" 應設定為 true。政策需要這項設定才能加入,因為它會啟用應用程式所有資源的交叉連結,而這在許多情況下並不合理。它們也假設任何特定範圍都可以存取其允許依存關係之上的任何範圍;所有模擬匯入地圖的範圍都必須設定 "cascade": true

匯入地圖僅有一個單一的頂層範圍,用於其「匯入」。因此,若要模擬 "imports",請使用 "" 範圍。若要模擬 "scopes",請使用 "scopes",其方式類似於 "scopes" 在匯入地圖中的運作方式。

注意事項:政策不會使用字串比對來尋找各種範圍。它們會執行 URL 遍歷。這表示 blob:data: URL 之類的項目可能無法在兩個系統之間完全互通。例如,匯入地圖可以透過在 / 字元上分割 URL,來部分比對 data:blob: URL,而政策則無法這麼做。對於 blob: URL,匯入地圖範圍不會採用 blob: URL 的來源。

此外,匯入地圖僅適用於 import,因此建議對所有依存關係對應加入 "import" 條件。

保證#
  • 政策保證在使用 require()import()new Module() 載入模組時,檔案的完整性。
  • 重新導向並不會透過直接存取 require.cache 等方法來防止存取 API,這些方法允許存取已載入的模組。原則重新導向只會影響 require()import 的指定項。
  • 在原則威脅模型中核准模組完整性表示它們在載入後可以修改甚至規避安全功能,因此預期會進行環境/執行階段強化。

基於程序的權限#

權限模型#

穩定性:1.1 - 積極開發

Node.js 權限模型是一種機制,用於在執行期間限制存取特定資源。此 API 存在於旗標 --experimental-permission 之後,啟用時,將限制存取所有可用的權限。

可用的權限由 --experimental-permission 旗標記載。

在使用 --experimental-permission 啟動 Node.js 時,將會限制透過 fs 模組存取檔案系統、產生程序、使用 node:worker_threads、原生附加元件,以及啟用執行階段檢查員的能力。

$ node --experimental-permission index.js
node:internal/modules/cjs/loader:171
  const result = internalModuleStat(filename);
                 ^

Error: Access to this API has been restricted
    at stat (node:internal/modules/cjs/loader:171:18)
    at Module._findPath (node:internal/modules/cjs/loader:627:16)
    at resolveMainPath (node:internal/modules/run_main:19:25)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:24)
    at node:internal/main/run_main_module:23:47 {
  code: 'ERR_ACCESS_DENIED',
  permission: 'FileSystemRead',
  resource: '/home/user/index.js'
} 

可以分別使用 --allow-child-process--allow-worker 來允許存取產生程序和建立工作執行緒。

若要在使用權限模型時允許原生附加元件,請使用 --allow-addons 旗標。

執行時期 API#

透過 --experimental-permission 標記啟用權限模型時,會在 process 物件中新增一個新的 permission 屬性。此屬性包含一個函式

permission.has(scope[, reference])#

API 呼叫會在執行時期檢查權限 (permission.has())

process.permission.has('fs.write'); // true
process.permission.has('fs.write', '/home/rafaelgss/protected-folder'); // true

process.permission.has('fs.read'); // true
process.permission.has('fs.read', '/home/rafaelgss/protected-folder'); // false 
檔案系統權限#

若要允許存取檔案系統,請使用 --allow-fs-read--allow-fs-write 標記

$ node --experimental-permission --allow-fs-read=* --allow-fs-write=* index.js
Hello world!
(node:19836) ExperimentalWarning: Permission is an experimental feature
(Use `node --trace-warnings ...` to show where the warning was created) 

兩個標記的有效參數為

  • * - 分別允許所有 FileSystemReadFileSystemWrite 作業。
  • 以逗號 (,) 分隔的路徑,分別僅允許符合的 FileSystemReadFileSystemWrite 作業。

範例

  • --allow-fs-read=* - 將允許所有 FileSystemRead 作業。
  • --allow-fs-write=* - 將允許所有 FileSystemWrite 作業。
  • --allow-fs-write=/tmp/ - 將允許對 /tmp/ 資料夾進行 FileSystemWrite 存取。
  • --allow-fs-read=/tmp/ --allow-fs-read=/home/.gitignore - 允許對 /tmp/ 資料夾/home/.gitignore 路徑進行 FileSystemRead 存取。

也支援萬用字元

  • --allow-fs-read=/home/test* 將允許讀取存取符合萬用字元的所有內容。例如:/home/test/file1/home/test2

傳遞萬用字元 (*) 之後,所有後續字元都將被忽略。例如:/home/*.js 的作用類似於 /home/*

權限模型限制#

在使用此系統之前,您需要了解一些限制

  • 模型不會繼承子節點程序或工作執行緒。
  • 使用權限模型時,下列功能將受到限制
    • 原生模組
    • 子程序
    • 工作執行緒
    • 檢查器協定
    • 檔案系統存取
  • 權限模型會在 Node.js 環境設定後初始化。然而,某些旗標(例如 --env-file--openssl-config)會在環境初始化之前讀取檔案。因此,這些旗標不受權限模型的規則約束。
  • 在啟用權限模型時,無法在執行階段要求 OpenSSL 引擎,這會影響內建的加密、https 和 tls 模組。
限制和已知問題#
  • 啟用權限模型時,Node.js 可能會以與停用時不同的方式解析一些路徑。
  • 不支援透過 CLI 使用相對路徑(--allow-fs-*)。
  • 即使是已授予存取權限的路徑集之外的位置,也會追蹤符號連結。相對符號連結可能會允許存取任意檔案和目錄。在啟用權限模型時啟動應用程式時,您必須確保沒有已授予存取權限的路徑包含相對符號連結。