2026 年 5 月 11 日,TanStack 的 npm 包被打了一次供应链攻击。数字不小:42 个 @tanstack/* 包,84 个恶意版本,发布时间集中在 19:20-19:26 UTC。
最反常的地方不是攻击多新,而是它多“顺”。没有证据显示 TanStack 的 npm 凭据被盗。攻击者也不是攻破了正常 publish step。它借的是 GitHub Actions release workflow 里的 id-token: write,直接走 npm OIDC trusted publishing 发包。
门没被撬。门禁系统被拿来开门。
谁受影响,应该立刻做什么
这次不是所有 @tanstack/* 包都确认中招。TanStack 扫描了 295 个包,确认受影响的是 42 个包、84 个版本。
已确认干净的系列包括:@tanstack/query、@tanstack/table、@tanstack/form、@tanstack/virtual、@tanstack/store,以及 @tanstack/start meta-package。
| 问题 | 当前信息 |
|---|---|
| 攻击时间 | 2026-05-11 19:20-19:26 UTC |
| 影响范围 | 42 个 @tanstack/* npm 包,84 个恶意版本 |
| 发现方式 | 发布后约 20 分钟,由外部研究者报告 |
| 恶意行为 | install 生命周期执行,抓取本机和 CI 凭据 |
| 处置状态 | 受影响版本已 deprecated,已请求 npm security 移除 tarball |
| 干净系列 | query、table、form、virtual、store、start meta-package |
安装端风险不能轻描淡写。
恶意 payload 在 install 生命周期里跑。也就是说,只要开发者机器、CI runner、构建服务器在当天装到了受影响版本,就要按潜在失陷处理。
它会尝试抓 AWS、GCP、Kubernetes、Vault、GitHub、npm、SSH 等凭据。还会枚举受害者名下维护的其他 npm 包,尝试继续传播。
最该动手的是两类人。
| 对象 | 该做什么 |
|---|---|
| 5 月 11 日安装过受影响版本的团队 | 视安装主机为潜在失陷,轮换云、GitHub、npm、SSH、Vault、Kubernetes 相关凭据 |
| 使用 GitHub Actions 发布 npm 包的维护者 | 检查 pull_request_target、Actions cache、OIDC trusted publishing 权限边界 |
这里不要赌“我应该没事”。供应链攻击最麻烦的地方,就是你很难从安装日志里一眼看出凭据有没有被拿走。
攻击链:三个合理功能,拼成一条发布通道
这次攻击链分三段。每一段单独看都不罕见,连起来就麻烦了。
| 环节 | 发生了什么 | 风险点 |
|---|---|---|
| PR 阶段 | fork PR 触发 pull_request_target,并执行 fork 代码 | 不可信代码进入 base repo 信任环境 |
| 缓存阶段 | 恶意代码污染 pnpm store,Actions cache 保存到主仓库作用域 | PR 脏缓存可被 main 分支复用 |
| 发布阶段 | release workflow 恢复脏缓存,恶意代码从 runner 内存提取 OIDC token | 不走正常 publish step,也能向 npm 发包 |
攻击者先开了一个看似无害的 PR。恶意 commit 进入 PR 后,bundle-size.yml 在 pull_request_target 语境下跑构建,执行了 fork 代码。
接着,污染过的 pnpm store 被写进 GitHub Actions cache。
更阴的是,攻击者又把 PR 强推回 main HEAD,让表面上变成 0 文件改动,然后关掉分支。代码痕迹淡了,缓存还在。
几个小时后,维护者合并正常 PR,main 分支触发 release workflow。它恢复了那份被污染的缓存。
release workflow 本来合法配置了 npm OIDC trusted publishing 所需的 id-token: write。恶意代码就在 runner 内存里提取 OIDC token,绕过失败的正式发布步骤,把 84 个恶意版本发到 npm registry。
这不是全新 0day。材料里提到的关键手法,能在既有公开研究里找到影子:2024 年的 Actions cache poisoning 研究,2025 年 tj-actions 事件里的内存提取 token 技术。
攻击者做的事更像拼装。把公开拼图拼到一条开源发布流水线上。
真问题:默认便利之间没有硬墙
我不太买账“某个维护者手滑”这种解释。
具体失误当然有。pull_request_target 不该随便跑 fork 代码。cache 不该跨信任边界复用。发布 workflow 的 OIDC 权限也不该让被污染依赖轻易碰到。
但只盯一个 YAML 文件,反而会看小这件事。
pull_request_target 有正当理由。机器人要评论,要打标签,要读 base repo 上下文。Actions cache 也有正当理由,能省构建时间。OIDC trusted publishing 甚至是安全改进,因为它避免长期保存 npm token。
坏就坏在这些“好东西”之间缺少隔离。
古话说,天下熙熙,皆为利来。放到 CI/CD 里,利不只是钱,也是少配一个密钥、少等几分钟、少维护一套发布流程。攻击者盯的就是这些省事路径。
这件事也给 npm OIDC trusted publishing 提了一个现实限制:它减少了长期 token 泄露,不等于消灭发布权限风险。只要 runner 上有不可信代码,短期凭据一样能变成武器。
接下来最该观察三件事。
| 观察点 | 为什么重要 |
|---|---|
| npm 是否移除恶意 tarball | deprecated 只能阻止误装,已缓存和镜像里的包仍要处理 |
| GitHub Actions cache 是否收紧跨边界复用 | cache 是这次从 PR 走到 release 的关键桥 |
开源项目是否重审 pull_request_target 和 OIDC 权限 | 这是维护者最能立刻改的部分 |
对维护者来说,短期动作很明确:不要让 fork PR 写入 base repo cache;不要让 release workflow 复用不可信路径产生的缓存;OIDC 权限按 workflow 收窄;publish 结果要监控,不能等外部研究者报案。
对工程团队来说,采购或引入依赖时也该多问一句:这个包怎么发布?谁能触发发布?发布机器上会不会跑 PR 代码?
过去大家看开源包,常看 stars、下载量、维护活跃度。现在还得看发布流水线。包本身没问题,不代表发包路径没问题。
这次 TanStack 的响应不算慢,外部社区发现也快。真正刺眼的是,攻击者没有跑赢技术前沿,只是沿着默认便利走了一遍。
自动化发布越顺,越要查清楚:这条路只给维护者走,还是也给攻击者留了入口。
