配了多年npm install,今天才知道每次装依赖都在跑别人的代码——npm v12把16年的默认开关彻底关了
配了多年 npm install,今天才知道每次装依赖都在跑别人的代码——npm v12 把 16 年的默认开关彻底关了
2026 年 7 月,npm v12 正式版发布。从这个版本开始,npm install 默认不再执行 preinstall、install、postinstall 脚本——除非你在 package.json 里明确用 allowScripts 白名单批准。这是 npm 诞生 16 年来最大的一次安全默认变更,影响的是每一个 JavaScript 项目的 CI/CD 流水线。
之前为什么一直是”后门”
npm 长期默认执行依赖包里的 install 脚本。这个设计初衷是为 node-gyp 这样的原生模块编译准备的——odbc、sharp、bcrypt 这些包需要在你机器上编译 C/C++ 代码,install 脚本负责调用 node-gyp。
但这个”方便”同时也是供应链攻击的入口。攻击者只需发布一个版本,在 postinstall 里跑恶意代码,等着其他人 npm install 触发。2026 年 7 月的 Shai-Hulud 事件第二次爆发,就是通过 npm 包的 postinstall 钩子实现的横向渗透。
在 npm v12 之前,这个攻击路径对所有项目默认敞开。
npm v12 的改变:从警告到拦截
npm 11.16.0(2026 年 6 月)开始推警告,让你提前知道哪些包需要审批:
npm warn allow-scripts 1 package has install scripts not yet covered by allowScripts:
npm warn allow-scripts odbc@2.5.0 (install: node-gyp rebuild)
npm warn allow-scripts
npm warn allow-scripts Run `npm approve-scripts --allow-scripts-pending` to review, or `npm approve-scripts <pkg>` to allow.
npm v12(2026 年 7 月)默认直接拦截未经审批的脚本。未批准的包 install 脚本不执行,npm install 仍然返回 exit code 0,但native addon 编译被跳过,后续 require() 运行时会报 “cannot find module X.node”。
这个静默失败是最危险的地方——CI 流水线用 exit code 0 判断成功,npm install 跑完,流水线显示通过,但部署出去的是一个跑不起来的项目。
迁移三步走
第一步:升级到 npm 11.16.0+,在当前项目跑:
npm install npm@latest -g
npm install # 看警告输出
npm approve-scripts --allow-scripts-pending
这条命令列出所有需要审批的包,显示每个包的 install 脚本内容。
第二步:逐个审批信任的包:
npm approve-scripts odbc # 信任 odbc 的 node-gyp 编译
npm approve-scripts sharp # 信任 sharp 的二进制下载
npm deny-scripts untrusted-pkg # 拒绝不明来源的脚本
审批结果写入 package.json:
{
"allowScripts": ["odbc", "sharp"],
"dependencies": {
"odbc": "^2.5.0",
"sharp": "^0.33.0"
}
}
第三步:处理 CI/CD 流水线。 如果流水线里有 native addon,在 npm install 之后加一步显式触发:
# 原来可能只有这一行
npm install
# v12+ 需要显式触发 native addon 的 postinstall
npm approve-scripts $(cat package.json | jq -r '.allowScripts | join(",")')
npx playwright install # 替代 postinstall 里的浏览器下载
npx cypress install # 同上
monorepo 要注意:allowScripts 字段按 workspace 独立生效,每个子包都要单独审批。
这次影响哪些包
postinstall 被 npm v12 默认拦截的高频依赖:
| 包 | 用途 | 替代方案 |
|---|---|---|
| sharp | 图片处理 | npx sharp install |
| bcrypt | 密码哈希 | npm approve-scripts bcrypt |
| canvas | 画布渲染 | npm approve-scripts canvas |
| sqlite3 | SQLite 绑定 | npm approve-scripts sqlite3 |
| fsevents | macOS 文件监控 | 只在 macOS 上需要 |
| @playwright/test | 浏览器自动化 | npx playwright install |
| @cypress/test | E2E 测试 | npx cypress install |
node-gyp 相关的包(protobuf、cproto、node-sass)如果只有 binding.gyp 而无显式 install 脚本,在 v12 里也会被 block——npm 对 binding.gyp 有一个隐式 node-gyp 调用,同样视为 install 脚本处理。
现在该做什么
已经在跑 npm 11.16.0+ 的项目,立刻跑一次 npm approve-scripts --allow-scripts-pending,把结果 commit 到仓库。未出现在列表里的包,要么没有 install 脚本,要么已经在 allowScripts 里。
7 月后升级到 v12 的项目,如果构建开始报 “cannot find module” 错误,先查依赖的 package.json 里有没有 "allowScripts": [...],没有就是被 v12 block 了。
把 allowScripts 字段加入 code review 清单——每次新增有 native 依赖的包,都要记得同步加到 allowScripts 里。
评论区
登录后可评论。