跳到正文
DEV Community· Posting Dude·· 1 小時前精選AI 評分80

npm 12 預設封鎖安裝腳本,但 Node 26 LTS 仍隨附 npm 11

npm 12 Blocks Install Scripts by Default. Node 26 LTS, Out Oct 28, Still Ships npm 11.

AI 導讀

npm 12 預設封鎖依賴套件的安裝腳本,但 10 月 28 日成為 LTS 的 Node 26 隨附 npm 11.20.0,安裝 Node 後若不另行升級 npm,安裝腳本仍會執行。

推薦理由

文章實測比較 npm 10、11、12、pnpm 與 bun 的安裝腳本預設行為,並梳理 Node LTS 和 CI 所用 npm 版本對開發環境防護的影響。

正文 · 繁體中文

On October 8,tensorlake@0.5.144 在 npm 上架了約 1 小時 40 分鐘。這並非正式發佈。有人取得維護者 GitHub 帳戶的存取權限後,提交了一個酬載並執行專案本身的發佈工作流程,因此套件甚至帶有有效的 npm 來源證明。preinstall 鈎子在開發者電腦上安裝套件的一刻便執行了酬載。

酬載會竊取 GitHub 和 npm 權杖、AWS 和 GCP 憑證、SSH 金鑰、.env 檔案及瀏覽器密碼,然後利用這些權杖把自身重新發佈到其他套件。它亦安裝了一個名為 gh-token-monitor 的小型服務;如果被盜的 GitHub 權杖遭撤銷,該服務便會刪除你的主目錄(SafeDep 發現,它只會在權杖所屬帳戶沒有任何組織時啟動)。以上內容都載於維護者自己的 GitHub 安全公告(GHSA-8g63-53c9-65j2),而這亦是自 April 起持續攻擊 npm 的 Mini Shai-Hulud 行動的一部分。

npm 已有修正「安裝時執行」問題的方法。npm 12 自 July 8 起推出,除非專案允許,否則不會執行相依套件的 preinstall、install 或 postinstall 指令碼。

問題在於,Node 26 於 October 28 成為 LTS,而隨附的 npm 版本是 11.20.0,並非 npm 12。我今天查看了 Node 發佈索引中所有現行 LTS 版本隨附的 npm 版本:

Node 版本 日期 隨附的 npm 版本
26.13.0 (LTS "Lithium") Oct 28, 2026 11.20.0
24.22.2 (LTS "Krypton") Oct 27, 2026 11.19.1
22.23.4 (LTS "Jod") Oct 20, 2026 10.9.9

目前只有 27 alpha 這條 Node 版本線隨附 npm 12。因此,如果你以一般方式安裝 Node,從不執行 npm install -g npm,所用版本仍會執行專案目錄內的所有安裝指令碼。

各套件管理工具實際上的行為

我想親自測試,而不是隻相信發佈説明,所以我在一個測試環境中建立了一個小型本機套件,其 preinstall 會在暫存目錄寫入標記檔案。我把它和 esbuild 一起安裝(這是一個一般、無害而且有 postinstall 的套件),分別使用各種工具安裝,然後檢查標記檔案是否存在。

工具 指令碼有執行嗎? 輸出內容
npm 10.9.9(Node 22 隨附的版本) 有 完全沒有
npm 11.21.1 有 一則列出兩個套件的警告
npm 12.2.1 沒有 「已封鎖 2 個套件的安裝指令碼」
pnpm 10.34.6 沒有 「已忽略建置指令碼:esbuild@0.25.11、harmless-preinstall@file:...」
bun 1.4.2 沒有 「已封鎖 1 個 postinstall」

有兩點特別明顯。npm 10 完全沒有任何跡象顯示指令碼曾執行。npm 11 的警告讀起來像是在提醒你未來會有變更,但標記檔案已經存在,表示指令碼早已執行。此外,指令碼還有一個 console.log,但在 npm 10 或 11 的安裝輸出中都看不到那一行。你看不到安裝指令碼執行。

pnpm 和 bun 預設已會封鎖指令碼。如果你使用其中一個,這篇文章的大部分內容都和你的 CI 及團隊成員有關,而不是你本人。

10 秒檢查專案

這段內容會列出你的 npm 版本、安裝指令碼是否已啟用、lockfile 中哪些套件確實有安裝指令碼(npm 會在 package-lock.json 中記錄 hasInstallScript),以及這部電腦上是否存在任何 tensorlake 持久化檔案。

#!/usr/bin/env bash
# install-scripts-check.sh: who gets to run code when you type npm install?
# usage: bash install-scripts-check.sh [project-dir]   (needs jq)
cd "${1:-.}" || exit 1
v=$(npm -v); major=${v%%.*}; ign=$(npm config get ignore-scripts)
allow=$(jq -c '.allowScripts // empty' package.json 2>/dev/null)
echo "node $(node -v), npm $v, ignore-scripts=$ign"
if [ "$major" -ge 12 ]; then echo "OK   npm 12+: dependency install scripts blocked unless allowed"
elif [ "$ign" = "true" ]; then echo "OK   ignore-scripts=true: install scripts off"
else echo "WARN npm $major runs every dependency install script (it only warns)"; fi
[ "$ign" = "true" ] && [ -n "$allow" ] && echo "WARN ignore-scripts=true silently overrides allowScripts"
echo "allowScripts: ${allow:-none}"
if [ -f package-lock.json ]; then
  n=$(jq '[.packages | keys[] | select(. != "")] | length' package-lock.json)
  echo "lockfile: $n packages; these have install scripts:"
  jq -r '.packages | to_entries[] | select(.value.hasInstallScript == true)
    | "  \(.key | sub("^.*node_modules/"; ""))@\(.value.version)"' package-lock.json
fi
echo "tensorlake worm persistence (all should be missing):"
for f in ~/.config/gh-token-monitor ~/.local/bin/gh-token-monitor.sh \
         ~/Library/LaunchAgents/com.user.gh-token-monitor.plist; do
  if [ -e "$f" ]; then echo "  FOUND $f  <- remove BEFORE revoking GitHub tokens"
  else echo "  missing $f"; fi
done
systemctl --user list-unit-files 2>/dev/null | grep -q gh-token-monitor \
  && echo "  FOUND gh-token-monitor systemd user service <- stop it first"
for f in .claude/settings.json .vscode/tasks.json; do
  grep -qE 'SessionStart|folderOpen' "$f" 2>/dev/null && echo "  CHECK $f runs a command on open. Did you write it?"
done
exit 0

我用這部電腦上的 npm,對一個相當常見的小型 SaaS 技術組合(next、react、react-dom、prisma 7、@prisma/client 7、stripe、tailwindcss、sharp)的新 lockfile 執行了這段內容:

node v24.22.1, npm 11.21.1, ignore-scripts=false
WARN npm 11 runs every dependency install script (it only warns)
allowScripts: {"prisma@7.10.1":true,"@prisma/engines@7.10.1":true}
lockfile: 191 packages; these have install scripts:
  @prisma/engines@7.10.1
  prisma@7.10.1
tensorlake worm persistence (all should be missing):
  missing /home/box/.config/gh-token-monitor
  missing /home/box/.local/bin/gh-token-monitor.sh
  missing /home/box/Library/LaunchAgents/com.user.gh-token-monitor.plist

這份 allowScripts 清單已經存在,因為我先用 npm 12 核准了 Prisma(見下一節)。這段內容在 npm 11 上仍顯示 WARN,這樣是合理的。在 11.21.1 上,你明確拒絕的套件確實會被略過(我測試過),但任何新出現而不在清單上的套件仍會執行。只有 npm 12 才會改變預設行為。把 npm 12.2.1 放在 PATH 最前面後,同一個專案會得到 OK npm 12+: dependency install scripts blocked unless allowed。

值得留意的數字:鎖定檔中有 191 個套件,只有 2 個設有安裝指令碼。你的允許清單大概會很短,短得足以逐項審閲。

升級至 npm 12 而不會令建置失效

npm install -g npm@12
npm install                      # blocked scripts get listed at the end
npm install-scripts ls           # same list, read-only
npm install-scripts approve prisma @prisma/engines
npm install-scripts deny some-package-you-dont-trust
npm rebuild                      # runs the scripts you just approved
git add package.json && git commit -m "allowScripts for npm 12"

上面的入門專案中,npm 12 列出了 @prisma/engines@7.10.1 (postinstall: node scripts/postinstall.js) 和 prisma@7.10.1 (preinstall: node scripts/preinstall-entry.js)。完成核準步驟後,package.json 有:

"allowScripts": {
  "prisma@7.10.1": true,
  "@prisma/engines@7.10.1": true
}

核准預設會固定在你審閲過的版本。Prisma 升級時,新版本會再次被封鎖,直至你檢視為止。這有點麻煩,但正正是用意所在。你信任的套件若有修補版本遭劫持,蠕蟲便會以此散播。npm install-scripts prune 會清除你已不再使用版本的條目。

GitHub 的 npm v12 重大變更説明涵蓋了同時改動的另外兩項預設設定:除非你允許,否則不會再解析 Git 相依項目和遠端 tarball URL。升級後,如果安裝 github: 相依項目時出現問題,原因就在這裏。

三個注意事項

ignore-scripts=true 的優先級高於你的允許清單。在 .npmrc 中設定這項設定,是 2025 年 9 月第一波 Shai-Hulud 蠕蟲出現後常見的安全強化建議。在 npm 12 中,它會在不作提示的情況下優先於 allowScripts 生效。我核准了測試套件,執行 npm install --ignore-scripts,但標記檔沒有出現。不加該旗標執行相同的安裝流程,標記檔便出現了。選擇一種設定方式,並移除另一種。

CI 使用自己的 npm。在手提電腦上升級,對建置伺服器毫無影響。Cloudflare 10 月 23 日的更新日誌指出,他們的 Workers Builds 和 Pages 映像現已搭載 Node 22.23.4 和 24.22.1;根據 Node 發佈索引,這些版本隨附 npm 10.9.9 和 11.19.1。如果你的 CI 使用類似 actions/setup-node 的方式設定 Node,除非你升級 npm,否則就會使用該 Node 版本隨附的 npm。在 npm ci 之前加入 npm install -g npm@12 步驟,或者查看建置器使用的版本。

真正的目標是你的手提電腦。SafeDep 對 tensorlake 攻擊載荷的分析顯示,載入程式會檢查 CI、GITHUB_ACTIONS、GITLAB_CI 和 RUNNER_ENVIRONMENT,並在 CI 環境中直接退出。它鎖定的是存有你的 SSH 金鑰、.env 檔案,以及已登入瀏覽器的電腦。AI 編碼 Agent 也會在這部電腦上替你輸入 npm install,而你可能沒有查看套件名稱。我曾為一個個人專案整理了一些防止編碼 Agent 安裝無用套件或讀取機密資料的規則

如果你認為自己安裝了有問題的套件

處理次序很重要,而且跟你的直覺完全相反。

  1. 讓電腦中斷網絡連線。
  2. 在撤銷任何權限前,先移除 gh-token-monitor。安全公告列出了相關指令:Linux 上執行 systemctl --user disable --now gh-token-monitor.service,並刪除 ~/.config/gh-token-monitor/ 和 ~/.local/bin/gh-token-monitor.sh;macOS 上執行 launchctl bootout,移除 com.user.gh-token-monitor LaunchAgent,並刪除 plist。
  3. 在另一部乾淨的電腦上輪替權杖。
  4. 檢查你的權杖可以寫入的儲存庫和套件。tensorlake 的安全公告建議留意意料之外的發佈版本、作者欄為 claude 的提交,以及名為 dependabot/github_actions/format/setup-formatter 的分支。Sonatype 和 Socket 也會標示有變更的 .claude/settings.json 和 .vscode/tasks.json 檔案,因此腳本會對這些檔案執行 grep 搜尋。
  5. 使用乾淨的映像檔重建電腦。

npm 12 無法修補的問題

封鎖安裝指令碼只能關上一道門。當你 import 套件時執行的程式碼不受影響。10 月 9 日一則關於名為 testrrrd 的 npm 套件的 GitHub 安全公告,描述的正是這種情況:它會從 postinstall 發出相同的信標訊號,並在匯入時再從主檔案發出一次,因此單靠 --ignore-scripts 並不足以阻止它。如果有問題的版本進入你的鎖定檔,應用程式首次啟動時就會執行它。

所以鎖定檔仍然重要。請提交它,在 CI 中使用 npm ci,而且不要在依賴套件更新發布的同一小時內自動合併。這跟一夜之間被重新指向的 trivy-action 標籤帶出的教訓一樣:你信任的名稱,不等於你實際取得的程式碼。

對大部分小型專案來説,整個修正方案就是升級 npm、安裝一次、核准兩三個套件,然後提交 package.json。

如果你已經升級至 npm 12:最終有多少個套件列入你的 allowScripts?升級時又是哪個套件最先出問題?

來源:DEV Community · dev.to