TanStack npm 供应链攻击后,代码 Agent 还该不该默认放行 npm install?
上周这类问题还很容易被当成“前端生态自己的事”。但到了 2026-05-11 这波 TanStack npm 供应链攻击,判断门槛已经变了:如果你的代码 Agent、CI runner 或开发机还默认放行 npm install,而且同一进程还能读到云凭据、GitHub token、SSH key 或 ~/.npmrc,那它现在就不该被视为“低风险默认动作”。
短答案: npm install 权限现在更像生产写权限,不像普通依赖安装动作。更稳妥的做法是把“审包”和“执行安装”拆开:先用 npm pack 拉 tarball 做静态检查,再把真正的安装放进隔离环境,默认加 --ignore-scripts,只有白名单包再放开脚本执行。
这篇的最小复现实验(本文写作时,Node 22.22.0 / npm 10.9.4):
npm pack ./postinstall-proof-fixture
POSTINSTALL_MARKER=/tmp/default-marker npm install ./postinstall-proof-fixture-1.0.0.tgz
POSTINSTALL_MARKER=/tmp/ignore-marker npm install ./postinstall-proof-fixture-1.0.0.tgz --ignore-scripts
我本地看到的结果是:npm pack 不会触发测试包里的 postinstall,普通 npm install 会写出 marker 文件,而 npm install --ignore-scripts 不会。这不等于所有风险都消失了,但它足够说明一件事:你真正要先收口的,不是“能不能装包”,而是“装包时脚本能拿到什么、能往外发什么”。
如果你还没补代码 Agent 的基础隔离,可以先对照这篇 代码 Agent 沙箱最小验收清单。如果你最近在补整个开发工具链的风险面,也可以顺手翻一下 TG Hubs 的 devtools 主题页。
这次 TanStack 事件,真正该改的是 npm install 权限模型
截至 2026-05-14,TanStack 官方 GitHub Security Advisory 给出的关键信息已经很明确:2026-05-11 的 6 分钟窗口里,42 个 @tanstack/* 包被发布了 84 个恶意版本;安装命中这些版本时,payload 会在安装阶段执行,目标包括 AWS/GCP/Kubernetes/Vault 凭据、~/.npmrc、GitHub token、gh 配置、.git-credentials 和 ~/.ssh/ 私钥。
OpenAI 同一天的官方 RSS 条目《Our response to the TanStack npm supply chain attack》在这里值得引用,不是因为本文要展开 OpenAI 自身的客户端处置,而是因为它证明了:这类安装阶段的供应链事件会一路波及到上层产品、签名证书和更新节奏。OpenAI 甚至明确要求 macOS 用户在 2026-06-12 前完成应用更新。
所以这题对 AI builder、Agent workflow 团队的意义不是“以后少装 TanStack”,而是:
- 任何会自动跑
npm install的 Agent,都不该继续默认摸到长期凭据 - 任何把安装脚本当成普通构建步骤的 CI,都要补一次边界检查
- 任何“先让 Agent 试试,出问题再说”的开发流程,都该把依赖安装单独拉出来做验收
这和前阵子讲的镜像投毒问题是同一类运营面:供应链动作不该再和高价值凭据共居一台默认开放的执行器。 如果你更偏容器侧排查,可以再看那篇 KICS 镜像被投毒后团队该先查什么。
适合谁 / 不适合谁
适合谁:
- 让代码 Agent 自动装 npm 依赖、跑脚手架、修 PR 的团队
- 把
npm install放进 CI、预览环境、代码生成流水线的团队 - 维护内部 npm 镜像、私有包或共享 runner 的平台团队
不适合谁:
- 只想知道 TanStack 具体受影响版本号、并且已经完成逐包排查的纯前端项目
- 完全不让 Agent/CI 触发 npm 安装、也不在开发机保留高价值凭据的离线环境
npm install 权限怎么收口:先做这 4 步
第 1 步:把“审包”从“执行安装”里拆出来
TanStack 官方 advisory 已经给了一个非常实用的方向:先拉 tarball,再看包内容。 这一步的关键不是检测一切恶意代码,而是先把“会执行脚本的安装动作”降成“不会执行脚本的静态检查动作”。
最小做法可以从这两条命令开始:
npm pack <package>@<version>
tar -xzf <package-version>.tgz
我这轮本地复现里,npm pack 对带 postinstall 的测试包没有触发脚本;这正是它适合作为预检查入口的原因。你至少可以先看:
package/package.json里有没有额外的optionalDependencies- 包根目录有没有异常的大体积脚本文件
- 版本号是不是命中了公告里的受影响窗口
验收通过状态: 你能在不执行安装脚本的前提下拿到 tarball,并完成一次静态检查。
常见失败点: 团队把 npm install 当成“顺便看一下依赖内容”的唯一入口,结果把检查动作和执行动作绑死了。
第 2 步:把 Agent 默认安装命令改成 --ignore-scripts
如果你的代码 Agent 只是为了补类型、拉 SDK、生成 lockfile、更新 demo 依赖,默认没必要给它脚本执行权。
我本地做的最小复现实验是这样的:
POSTINSTALL_MARKER=/tmp/default-marker npm install ./postinstall-proof-fixture-1.0.0.tgz
POSTINSTALL_MARKER=/tmp/ignore-marker npm install ./postinstall-proof-fixture-1.0.0.tgz --ignore-scripts
结果很直接:普通安装写出了 marker;--ignore-scripts 那次没有写出 marker。也就是说,同一个包,是否给安装脚本执行机会,默认值就能决定一大截风险暴露面。
更适合今天先落地的做法是:
- Agent 路径默认使用
npm install --ignore-scripts - 必须执行脚本的包走白名单
- 白名单包的安装动作单独放进隔离 runner,不和通用补丁任务共用执行器
验收通过状态: Agent 的默认安装路径不再执行 lifecycle scripts。
常见失败点: 误以为 lockfile 足够安全;其实 lockfile 约束版本,不等于自动阻止安装脚本运行。
第 3 步:把长期凭据从安装进程旁边搬走
TanStack 官方 advisory 列出来的目标很说明问题:攻击者盯的不是“项目能不能编译”,而是安装进程边上的高价值材料。
如果你今天只能做一轮最小整改,优先收这几样:
- 把
~/.npmrc里的长期 token 换成短期或按 job 注入 - 不要让装包 runner 默认挂着云厂商长期 AK/SK
- 不要让执行安装的机器同时持有可推私仓的 SSH key
- 把
gh、.git-credentials、部署密钥和依赖安装解耦
这一步和 代码 Agent 批量提 PR 时 reviewer 该信什么证据链 是连着的:你不只是要让 Agent 能干活,还要让后续 reviewer 能看见它到底接触了哪些外部输入、拿到了哪些权限。
验收通过状态: 就算安装脚本被执行,它也读不到长期凭据和生产级推送能力。
常见失败点: 只关心 repo 只读、网络默认关闭,却忘了依赖安装往往发生在另一个更宽松的执行路径里。
第 4 步:把 incident 后的排查顺序固定下来
如果你的团队在 2026-05-11 当天确实跑过相关安装,不要只做“升级到 patched version”这一件事。更稳的顺序是:
- 先识别是否命中过受影响包和时间窗口
- 立即轮换安装进程可见的 token、SSH key、云凭据
- 回看 CI / 开发机 / 预览环境的出站访问和云审计日志
- 再决定哪些依赖安装路径要永久改成
npm pack预检 +--ignore-scripts默认
验收通过状态: 你的 incident runbook 里已经把“版本升级”和“凭据轮换 / 出站回看”拆成两步。
常见失败点: 团队只修版本,不回看凭据是否已经暴露。
哪些团队今天就该收,哪些可以后补
今天就该收的:
- 让 Agent 自动跑
npm install修 bug、补 demo、生成脚手架的团队 - 共用 CI runner、但还没把依赖安装和部署权限拆开的团队
- 开发机上同时放了 npm token、云凭据和 SSH key 的小团队
可以后补、但别拖太久的:
- 只在本地人工安装依赖、且开发机没有高价值凭据的个人项目
- 纯只读评审 Agent,不执行 npm 安装和脚手架命令的流程
FAQ
1. TanStack 这次事件里,单纯 npm install 就会中招吗?
按 TanStack 官方 GitHub Security Advisory 的描述,只要安装命中了受影响版本,payload 就会在安装阶段执行,所以这次问题的关键不在“运行你的业务代码”,而在“你有没有给安装阶段脚本执行机会”。
2. package-lock.json 能不能解决这类风险?
不能单独解决。lockfile 主要解决版本可重复,不自动阻止 lifecycle scripts,也不自动隔离安装进程旁边的 token、SSH key 和云凭据。
3. npm install --ignore-scripts 会不会把很多包装坏?
会有兼容性成本,所以更现实的做法不是全公司一刀切,而是:Agent 默认 --ignore-scripts,再为确实依赖安装脚本的包做白名单和隔离执行。
4. npm pack 能不能替代真正安装?
不能。npm pack 更像预检查入口:它适合先看 tarball、manifest 和可疑文件,但不等于已经验证运行时、编译链或真实依赖树都安全。
5. 如果我们已经做了 repo 只读和默认无网,还需要再管 npm install 权限吗?
需要。repo 只读和默认无网解决的是一部分执行边界;npm install 权限解决的是安装脚本能否执行、执行时能读到什么凭据。这两层不是互相替代,而是前后相接的最小控制面。
结论
TanStack 这次事件最值得团队立刻改的,不是再记一次受影响版本号,而是把 npm install 权限当成生产动作来管。对代码 Agent、CI 和共享开发环境来说,今天最实用的最小闭环就是:先 npm pack 审包,默认 npm install --ignore-scripts,把真正需要脚本执行的安装放进隔离 runner,再把长期凭据从安装进程旁边搬走。
如果你的代码 Agent 还在默认路径里直接跑 npm install,那现在最该补的一次排查,不是“它能不能装成功”,而是“它装成功时到底拿到了什么权限”。