正文

升级到 npm v12 后,如果你遇到这些现象,先不要急着删 lockfile 或把所有脚本重新打开:

这通常不是某个依赖突然坏了。npm v12 把安装时的安全默认值改成了更保守的模式:依赖的 preinstallinstallpostinstall 和隐式 node-gyp 构建默认不执行;Git 依赖和远程 URL 依赖默认也不再解析。官方建议先审核要运行的脚本,再把允许列表提交到 package.json

Text
安装完成了,但 Prisma / esbuild / sharp 不能运行
node-gyp 没有编译,原生模块加载失败
依赖里写了 git+https 地址,npm install 却解析不到
CI 在 npm install 阶段失败,本地旧 npm 却正常

npm v12 改了哪几类安装行为

先判断你遇到的是哪一类阻断。它们看起来都像“安装失败”,但处理方式不同。

这些默认行为在 npm 11.16.0 之后已经以警告方式出现;npm v12 被标记为 latest 后,原来的自动执行行为改为显式放行。

安装后缺少生成文件或二进制生命周期脚本默认关闭
原生模块无法加载隐式 node-gyp 构建默认不运行
git+https / GitHub commit 依赖无法安装Git 依赖默认不解析
HTTPS tarball 依赖无法安装远程 URL 依赖默认不解析

先确认是不是 npm v12 导致

不要只看 Node.js 大版本。先在报错的本机或 CI 环境中确认 npm 实际版本:

如果 CI 和本地的 npm 主版本不同,先统一它们。GitHub Actions 可以把 Node 版本固定在工作流中,再让每个环境从同一份 lockfile 安装:

npm ci 只保证按 lockfile 安装,不会绕过 npm v12 的安全默认值。CI 能不能执行依赖脚本,仍取决于项目提交的放行配置和运行时参数。

Bash
npm --version
node --version
npm config get allow-scripts
npm config get allow-git
npm config get allow-remote
YAML
- uses: actions/setup-node@v4
  with:
    node-version: 22
    cache: npm

- run: npm ci

postinstall 或 node-gyp 没跑时,先审核再放行

最容易出问题的不是项目自己的 prepare,而是传递依赖里的安装脚本。例如 esbuild 下载平台二进制、sharp 准备原生依赖、某些数据库客户端生成代码或原生模块触发 node-gyp 编译。

先在干净工作区执行 npm 提供的审核入口:

这一步不要直接全选。对每个待审批包,至少看三件事:

确认后,npm 会把允许列表写入项目配置。把生成的 package.json 变更和 lockfile 一起提交,确保开发机与 CI 使用同一份决定。

如果你只需要验证“是不是脚本没有执行”,可以在隔离分支中重新安装,然后检查受影响模块的实际输出:

成功标准不是 npm install 返回 0,而是生成步骤、构建和受影响的运行时测试都通过。原生模块场景还应在与部署一致的系统和 Node 版本中运行一次。

  • 包是否是 package.json 或 lockfile 中的预期依赖。
  • 脚本是否解释了用途,例如下载对应平台二进制或编译原生绑定。
  • 该包版本、维护者和来源是否符合团队当前依赖策略。
Bash
npm approve-scripts --allow-scripts-pending
Bash
npm ci
npm run build
npm test

Git 依赖安装失败时,不要先全局放开

项目里出现这类声明时,npm v12 可能会拒绝继续解析:

先查它是直接依赖还是传递依赖:

Git 依赖的风险不只在网络。它可能绕过 npm registry 的常规发布与审计流程,也更容易因为分支移动、提交被删除或访问令牌失效而让构建不可复现。

优先级应当是:

远程 tarball 依赖也按同一思路处理。确认完整 URL、校验来源和版本固定方式后,再决定是否允许;不要为了通过一次安装,把所有远程来源永久放开。

  • 能换成 registry 已发布的固定版本时,先换掉 Git 依赖。
  • 必须使用 Git 时,固定 tag 或 commit,不依赖会移动的分支名。
  • 只在确实需要的项目或 CI job 中显式允许 Git 来源,并记录原因。
  • 给私有仓库访问令牌最小权限,不把令牌写进 package.json 或 lockfile URL。
JSON
{
  "dependencies": {
    "internal-tool": "git+https://github.com/example/internal-tool.git#v2.4.0"
  }
}
Bash
npm ls internal-tool
npm explain internal-tool

CI 里怎样避免“本地能装、线上不能装”

把下面四项放进同一次变更里:

如果你维护 monorepo,不同 workspace 的脚本审批也要一起复核。不要因为某个 workspace 需要编译原生模块,就让整仓库所有传递依赖的安装脚本都自动运行。

可以在 CI 加一段只读检查,把偏差暴露在发布前:

任何需要网络下载、编译二进制或访问私有 Git 仓库的依赖,都应有一条能在 CI 日志中识别的失败信息。这样出现故障时,能分清是 npm 规则、凭据、平台 ABI,还是依赖本身的问题。

package.json依赖脚本允许列表与 engines
lockfile依赖版本与来源,避免 CI 重解依赖
CI 工作流Node/npm 版本、缓存 key、安装命令
镜像或部署环境与 CI 一致的 Node/npm 组合
Bash
npm --version
node --version
npm ci
npm run build

不要用这三种方式“修好”

在所有机器上关闭安全默认值

把脚本、Git 和远程来源全部恢复成无条件执行,会让一次升级故障变成长期供应链风险。先按具体依赖最小放行。

删除 lockfile 后反复重装

这会同时改变大量传递依赖,使问题更难定位。先保留 lockfile,确认安装策略;只有要有意识升级依赖时才更新它。

只让 CI 使用旧 npm

短期回退可以作为止血措施,但要设定撤销日期。否则开发机、CI 与发布镜像会继续漂移,下一次依赖升级仍会以更难排查的方式失败。

迁移完成后,发布凭据也要一起检查

npm v12 同时开始收紧可绕过 2FA 的 granular access token。敏感账户、组织和包管理操作将不能再通过这类 token 跳过 2FA;直接发布也会继续向 trusted publishing 或 staged publishing 迁移。

如果 CI 仍依赖长期存在的发布 token,现在就应安排迁移验证:

  • 使用 npm 的 trusted publishing(OIDC)或带人工 2FA 的 staged publishing。
  • 把安装凭据和发布凭据分开,不让普通安装 job 拥有 publish 权限。
  • 在测试包或 prerelease tag 上走完整发布链,再迁移正式包。

结论

npm v12 的变化不是让依赖安装变得不可用,而是要求项目明确声明:哪些安装脚本、Git 来源和远程来源值得信任。

遇到安装异常时,按“确认 npm 版本 -> 定位具体依赖 -> 审核并最小放行 -> 在 CI 和部署环境验证”的顺序处理。这样既能恢复 postinstallnode-gyp 和必要 Git 依赖,也不会把升级变成无边界的脚本执行入口。

参考来源

npm install-time security and GAT bypass2fa deprecationGitHub Changelognpm approve-scriptsnpm Docsnpm installnpm DocsLifecycle scriptsnpm DocsTrusted publishing with OIDCnpm Docs

相关文章

npm 供应链攻击后,开发者要怎么检查依赖、lockfile 和 CI 密钥工程实践 / 约 13 分钟GitHub Actions 不是能跑就行:第三方 Action、secrets、权限和日志怎么查工程实践 / 约 13 分钟Vite 7 升级卡住:为什么要求 Node 20.19+ / 22.12+工程实践 / 约 15 分钟AI Agent 让你安装依赖时,哪些包不能直接允许智能编程 / 约 12 分钟

作者信息