npm v12 来了:allowScripts 默认关闭,我的项目差点跑不起来

上周五下午,我像往常一样在项目里跑 npm install,结果 CI 直接红了。报错信息是一堆"node-gyp rebuild failed",native addon 编译全挂。

第一反应是 node 版本不对,检查了一遍发现没问题。又以为是网络问题,挂代理重试,还是挂。最后翻日志才发现------npm 开始提示一堆以前从没见过的 warning,说什么"preinstall script will not run"。

我这才反应过来:npm v12 真的要来了,而我完全没有准备。

一个被忽视十几年的"特性"

说实话,在这次翻车之前,我从来没仔细想过 npm install 到底在干什么。

不就是下载依赖包吗?

直到这次报错逼我去翻文档,才发现 npm 一直是这么玩的:当你执行 npm install,npm 不只是下载文件,它还会自动执行包里的生命周期脚本。package.json 里声明的 preinstallinstallpostinstall,npm 都会自动跑。

很多包用这些脚本来编译原生模块、下载二进制资源、做环境初始化。比如 node-sass 要编译 C++ addon,sharp 要下载预编译的二进制,不跑脚本这些包根本没法用。

问题在于------这些脚本跑的时候,拿的是你当前进程的全部权限

你在 CI 机器上跑 npm install,postinstall 脚本可以直接读取环境变量里的 AWS credentials、SSH key、npm token。它想干什么就干什么,不需要任何确认。

我一直以为 npm install 是安全的。事实证明,这种信任可能从一开始就是个误会。

供应链攻击已经盯上这个入口

让我真正重视这件事的,是看到 GitHub 6月初发的公告。

他们说 npm v12 要在 7 月发布一个"史上最大安全变更":安装脚本以后不会自动执行,必须手动批准

配套的数据让我后背发凉:JavaScript 生态里超过 72% 的供应链攻击,都是通过安装脚本植入恶意代码实现的。

这不只是理论上的风险。就在今年6月,朝鲜黑客组织 Sapphire Slet 利用被劫持的 npm 维护者账号,在恶意包里植入后门,通过 postinstall 脚本收集开发者的 credentials。这波攻击影响了超过 140 个 npm 包。

想想你上周跑的 npm install,那个 postinstall 脚本到底干了什么?你真的清楚吗?

我之前从来没想过这个问题。

npm v12 要改什么

GitHub 这次决心很大,v12 的变更有三条,每条都是硬改:

第一条,allowScripts 默认关闭。

这是最核心的变更。以后 npm install 不会再自动跑 preinstall、install、postinstall 脚本,包括 node-gyp 触发的隐式编译。

也就是说,如果你的项目依赖 node-sass、sharp、canvas 这类需要编译原生模块的包,升级到 npm v12 之后,这些包装上了但没法用,因为编译脚本不跑了。

第二条,allow-git 默认值变成 none。

以前你可以直接在 package.json 里写 "dependencies": { "some-lib": "github:username/repo" },npm 会去 Git 拉代码。v12 之后这条路默认封死,必须显式开启。

第三条,远程 URL 依赖也砍了。

"some-lib": "https://some-cdn.com/file.tgz" 这种直接下载 tarball 的方式,v12 也不认了。所有包必须来自官方 registry。

这三个变更加在一起,就是把 npm 变成一个默认不信任任何东西的包管理器。想让什么东西跑,必须说清楚为什么信任它。

approve-scripts 是怎么工作的

npm 官方知道这个改动会很痛,所以给了一套工具让你平滑过渡。

核心命令是 npm approve-scripts

在 npm 11.16.0 以上的版本里,你可以先跑这个命令看看自己项目里有哪些包有安装脚本:

css 复制代码
npm approve-scripts --allow-scripts-pending

这个命令会扫描所有依赖,包括间接依赖,然后列出一个清单,告诉你哪些包想要跑脚本、跑的是什么脚本。

然后你需要一个个决定:信任还是不信任。

perl 复制代码
# 批准某个包的脚本
npm approve-scripts <package-name>

# 拒绝某个包的脚本
npm deny-scripts <package-name>

批准的结果会写进 package.json,像这样:

json 复制代码
{
  "name": "my-project",
  "scripts": {},
  "dependencies": {},
  "approvedScripts": {
    "node-sass": ["install"],
    "sharp": ["postinstall"]
  }
}

这个配置是项目级别的,意味着你的同事拉代码之后,npm 会自动识别哪些脚本是被批准过的。未批准的脚本?对不起,不跑。

我的项目是怎么修复的

回到我开头说的那个 CI 报错。

查了一圈发现,问题出在 sharp 这个图片处理库。sharp 在安装时会下载预编译的二进制文件,这个操作是通过 postinstall 脚本完成的。

在旧版 npm 下,这一步自动完成,没人会注意。到了 v12,不批准这个脚本,sharp 就装了个空壳,运行时直接报错。

修复步骤其实不复杂:

先升级 npm:

css 复制代码
npm install -g npm@latest

确认版本在 11.16.0 以上,然后跑 approve-scripts 扫描:

css 复制代码
npm approve-scripts --allow-scripts-pending

输出里找到 sharp,看到它需要一个 postinstall 脚本。确认这个脚本确实是下载二进制文件,没有其他操作,批准:

复制代码
npm approve-scripts sharp

这个命令把批准信息写进 package.json。然后 npm install 再跑一遍,sharp 的 postinstall 正常执行,问题解决。

整个过程大概花了十分钟,比我排查报错的时间还短。

但这个改动真的准备好了吗

我在 Twitter 上看到这个变更的讨论,反应挺两极的。

支持的人说这是"迟到十几年的安全加固",npm 早该像浏览器对待 JavaScript 一样默认关闭危险操作。也有人说 JavaScript 生态的包太乱了,很多老项目的 postinstall 脚本根本没人维护,升级之后直接跑不起来。

我的体感是,这两种声音都对。

npm 这次转向是对的,供应链攻击只会越来越频繁,默认信任的模式早该终结。但对于维护者来说,这个过渡期确实会有点痛。尤其是那些依赖很多 native addon 的项目,或者是接手了一个没人维护的老项目,approve-scripts 的清单可能长得让人绝望。

GitHub 给了 npm 11.16.0 以上的版本可以提前看警告,这个设计还算良心。但问题是,大多数开发者不会主动去看 warning,除非等到 CI 红了才反应过来。

你现在能做什么

如果你看到这篇文章,赶紧打开终端查一下自己的 npm 版本:

css 复制代码
npm --version

低于 11.16.0 的,先升级:

css 复制代码
npm install -g npm@latest

然后在你手头的项目里跑一下 approve-scripts,看看有哪些脚本需要审批:

css 复制代码
npm approve-scripts --allow-scripts-pending

这个清单越早处理越好。别等到 7 月 npm v12 发布那天才发现项目跑不起来了。

对于团队项目,建议把 approvedScripts 配置纳入 code review,确保每个被批准的脚本都经过审查。那些没人维护的依赖,能替换就替换,能去掉就去掉。

npm 正在从一个"下载安装二合一"的工具,变成一个真正的包管理器。这个转变有点疼,但长期来看应该是个好事。

只是在那之前,我们得先熬过这个过渡期。

相关推荐
叫我Paul就好1 天前
尝试 Node 搭建后端-开发框架
node.js
JuliusDeng2 天前
一文搞懂 `.npmrc`:npm 源、SSL 与 `_authToken` 配置避坑
npm·前端工程化
风止何安啊3 天前
网课倍速痛点解决:一套前端代码实现自由控速播放器
前端·javascript·node.js
糖拌西瓜皮3 天前
Node.js核心模块实战:文件、路径、HTTP与流处理
javascript·node.js
糖拌西瓜皮3 天前
Node.js工程化实践:包管理、TypeScript配置与代码质量
typescript·node.js
糖拌西瓜皮3 天前
NestJS入门指南:Java开发者的Spring Boot体验
javascript·node.js
糖拌西瓜皮3 天前
Express框架快速上手:中间件、路由与错误处理
javascript·node.js
半个落月3 天前
从 Tokenization 到 Embedding:用 Node.js 搞懂大模型为什么先“分词”再“向量化”
人工智能·node.js
叁两4 天前
前端转型AI Agent该如何学习?(前置篇)
前端·人工智能·node.js