Kenwea 追踪 npm 下载量前 5000 个包的安装脚本
We traced the install scripts of NPM's 5k most-downloaded packages
Kenwea 读取 npm 下载量前 5000 个包的最新版本,在无网络、只读文件系统的沙箱中逐个运行其 preinstall、install、postinstall 与 binding.gyp 步骤,并用 strace 记录网络连接、DNS 查询和程序调用。
安装脚本正是 npm 供应链攻击的藏身之处:这些代码会在你导入任何东西之前,在你的机器或 CI 运行器上执行。与其猜测生态系统中这样做的人有多少,我们进行了统计,并且对那些确实这样做的包,我们运行了它们的安装步骤并进行了观察。
我们是如何做的
这份列表来自 npm-high-impact 1.13.0 中下载量最高的 5,000 个包。我们在 2026 年 10 月 8 日从 npm registry 读取了每个包的最新版本(读取了 4,995 个 manifest,5 个失败)。每个声明了 preinstall、install 或 postinstall,或附带 binding.gyp 的包,都经过了 Kenwea notary 的生产沙箱:无网络、丢弃所有 Linux capabilities、只读文件系统、非 root 用户,安装在一个依赖它的项目的 node_modules 内,并带有 npm 提供给安装脚本的环境变量,每步 15 秒,每一步都在 strace 下监控其尝试的网络连接、DNS 查询和程序。依赖项未被安装。
5,000 个中只有 31 个在安装时运行任何东西
也就是 0.6%。对于几乎所有热门包,安装它不会运行其自身的任何代码。生态系统的安装时攻击面集中在一份很短的清单上,短到团队可以手工阅读。
这 31 个做了什么
- 13 个运行完成且未尝试连接任何东西:core-js、core-js-pure、es5-ext、vue-demi、nx、puppeteer、yarn、@google/genai(其 preinstall 只打印一行)、@firebase/util、@parcel/watcher、ssh2、@scarf/scarf 和 @anthropic-ai/claude-code。
- 13 个失败,几乎都是因为原生构建需要编译器、Python,或一个随我们未安装的依赖项一起提供的预编译二进制文件:pnpm、@pnpm/exe、unrs-resolver、@sentry/cli、msgpackr-extract、node-pty、cypress、cpu-features、canvas、bufferutil、utf-8-validate、lmdb 和 bcrypt。
- 3 个在启动包管理器以获取某些东西时,被我们的 15 秒限制截停:esbuild 和 @swc/core 会退回到运行 npm install 来获取其平台二进制文件,而 union 的 preinstall 会运行 npx npm-force-resolutions。在被截停时,它们都还没有发出 DNS 查询。
- fsevents 在 registry 中列有 node-gyp rebuild,但其 tarball 附带预编译二进制文件且没有 binding.gyp,因此不会运行任何东西。无论如何它仅适用于 macOS。
- aws-sdk 解包后超过了沙箱 96 MiB 的工作空间,因此未运行。公共 notary 不获取超过 10 MiB 的包,所以 aws-sdk 和 node-pty 在那里根本无法检查。
这说明什么,又不说明什么
- 在这里,没有任何包在其自身的安装步骤中尝试访问网络。这与安全不是一回事。依赖项未被安装,因此回退后下载的步骤被截短;脚本可以察觉自己在沙箱中并保持安静;而 @scarf/scarf 的职责是遥测,只有当它是另一个包的依赖项时才会报告,而单独检查该包时永远不会是这种情况。
- 有用的部分是集中度。31 个安装步骤可以由人工审查。5,000 个包则不能。
- 如果你的项目不需要安装脚本,npm install --ignore-scripts 会关闭它们,而 pnpm 10 已经默认阻止依赖项的生命周期脚本,除非你按名称允许它们。
自己检查一个
上述每个结果都可以复现,并且每个结果都会以使用 Kenwea 已发布密钥签名的记录返回,并在显示之前在你的机器上验证。在 CI 中,当依赖项在安装时运行任何东西或尝试访问网络时,门禁会让构建失败:
npx -y @kenwea/mcp check esbuild
npx -y @kenwea/mcp check esbuild --fail-on install-scripts,network所有 31 个软件包的原始结果,包括声明的脚本、结果以及每个步骤尝试的内容:https://www.kenwea.com/data/census-2026-10-08.json。该运行于 2026 年 10 月 8 日耗时 171 秒。软件包版本每天都会变化,因此重新运行结果会有所不同。
无需账户或密钥即可试用 notary,或将其作为 MCP 服务器添加到你的 agent 中。
来源:Hacker News · kenwea.com