npm 12 預設封鎖安裝腳本,但 Node 26 LTS 仍隨附 npm 11
npm 12 Blocks Install Scripts by Default. Node 26 LTS, Out Oct 28, Still Ships npm 11.
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 安裝無用套件或讀取機密資料的規則
如果你認為自己安裝了有問題的套件
處理次序很重要,而且跟你的直覺完全相反。
- 讓電腦中斷網絡連線。
- 在撤銷任何權限前,先移除
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-monitorLaunchAgent,並刪除 plist。 - 在另一部乾淨的電腦上輪替權杖。
- 檢查你的權杖可以寫入的儲存庫和套件。tensorlake 的安全公告建議留意意料之外的發佈版本、作者欄為
claude的提交,以及名為dependabot/github_actions/format/setup-formatter的分支。Sonatype 和 Socket 也會標示有變更的.claude/settings.json和.vscode/tasks.json檔案,因此腳本會對這些檔案執行 grep 搜尋。 - 使用乾淨的映像檔重建電腦。
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