← EasyTool.me

TanStack 官方 npm 供应链攻击复盘:三重漏洞链详解

发布日期:2026-05-12 阅读时间:8 分钟 安全 / 供应链攻击

事件背景

2026年5月11日,TanStack npm 生态系统遭遇重大供应链攻击。攻击者向 42 个 @tanstack 包发布了 84 个恶意版本,通过窃取 OIDC token 控制了项目自身的 GitHub Actions 发布管线。事件在 20 分钟内被外部研究员发现,但影响已非常深远——恶意版本携带着合法的 SLSA 来源证明,因为这些版本确实是由 TanStack 自己的 CI/CD 管线所发布。

事件发生后仅数小时,TanStack 创始人 Tanner Linsley 就发布了这份完整的攻击复盘报告。文章一发出就在 Hacker News 上冲到了 613 分232 条评论,足见开发者社区对这次攻击的高度关注。

注:我们此前已报道过该攻击的初始发现过程,本文聚焦 TanStack 官方发布的完整复盘报告,深入分析攻击的技术细节和漏洞链。

三重漏洞链:没有任何一环单独足够

复盘报告明确指出,这次攻击不是利用了单一漏洞,而是 三个已知安全问题串联在一起 的结果。每个环节本身都无法单独完成攻击,但组合在一起就变成了一把武器。

第一环:pull_request_target "Pwn Request" 模式

TanStack 仓库中的 bundle-size.yml 工作流使用了 pull_request_target 事件触发器。这个事件的设计初衷是让来自 fork 的 PR 能够以 基础仓库的权限上下文 运行——常用于标签、评论等操作。

问题在于,这个工作流 checkout 了 fork PR 的合并代码 并执行了构建步骤:

on:
  pull_request_target:
    paths: ['packages/**', 'benchmarks/**']

jobs:
  benchmark-pr:
    steps:
      - uses: actions/checkout@v6.0.2
        with:
          ref: refs/pull/${{ github.event.pull_request.number }}/merge
      - uses: TanStack/config/.github/setup@main
      - run: pnpm nx run @benchmarks/bundle-size:build

工作流作者尝试做了权限分离(隔离了一个 comment-pr job),但忽略了关键问题:actions/cache@v5 的 post-job save 不受 permissions: 设置的限制。缓存写操作使用的是 runner 内部 token,而非工作流的 GITHUB_TOKEN。这意味着即使设置了 permissions: contents: read,也无法阻止缓存被污染。

第二环:GitHub Actions 缓存投毒跨信任边界

恶意代码 vite_setup.mjs(约 3 万行 JS 包)专门设计用来将投毒数据写入 pnpm-store 目录,缓存的 key 与合法 release 工作流将使用的 key 完全一致:

Linux-pnpm-store-${hashFiles('**/pnpm-lock.yaml')}

当 benchmark-pr job 结束时,actions/cache@v5 的 post-step 将这个(已投毒的)pnpm store 保存到了缓存中。当 release.yml 下一次在主分支上运行时,它的 Setup Tools 步骤会恢复这个缓存——一切都在 GitHub Actions 的正常工作流程内完成。

这是一种业界熟知但经常被忽视的攻击手法。早在 2024 年,安全研究员 Adnan Khan 就详细记录过这类攻击。它不针对特定项目,而是 GitHub Actions 缓存系统自身的设计问题。

第三环:OIDC Token 进程内存提取

这是最巧妙也最危险的一环。TanStack 的 release.yml 声明了 id-token: write 权限——这对 npm OIDC 可信发布者是必需的合法配置。当投毒的缓存被 restore 后,攻击者控制的二进制文件在构建步骤中执行:

  1. 通过 /proc/*/cmdline 定位 GitHub Actions Runner.Worker 进程
  2. 通过 /proc/*/maps/proc/*/mem 转储工作进程内存
  3. 提取运行时生成的 OIDC token(当 id-token: write 设置时,runner 会在内存中延迟生成该 token)
  4. 使用该 token 直接向 registry.npmjs.org 发送认证 POST 请求
  5. 绕过工作流的 Publish Packages 步骤,以项目自己的身份发布恶意包

值得注意的是,这种内存提取技术(以及几乎相同的 Python 脚本,连署名注释都保留了)曾在 2025 年 3 月的 tj-actions/changed-files 事件中被使用过。攻击者并没有发明新的攻击手法,而是巧妙地将已知研究重新组合。

完整攻击时间线

复盘报告提供了精确到分钟的时间线:

缓存投毒阶段(5月10日-11日)

恶意发布阶段(5月11日)

发现与响应

恶意 Payload 做了什么

当开发者或 CI 环境对受影响的版本执行 npm install / pnpm install / yarn install 时,npm 解析恶意 optionalDependencies 条目,获取 fork 网络中的孤立 payload commit,运行其 prepare 生命周期脚本,执行约 2.3MB 混淆的 router_init.js

受影响包清单

42 个包每个被发布了两个恶意版本(间隔约 6 分钟),总共 84 个版本。未被影响的系列包括 @tanstack/query*@tanstack/table*@tanstack/form*@tanstack/virtual*@tanstack/store

IOC 指标(供安全团队使用)

复盘核心教训

做得好的地方

可以做得更好的地方

结语

这次攻击没有使用任何零日漏洞或秘密后门。它纯粹是 已知风险的手工组合——pull_request_target + GitHub Actions 缓存投毒 + OIDC token 内存提取,这三个已知问题各自被认为是"不那么致命"的,但串联在一起就变成了足以攻陷整个生态的武器。

对于所有在 GitHub Actions 上运行 CI/CD 的项目,无论规模大小,TanStack 的这次复盘都是一份宝贵的安全教材。特别是那些使用 pull_request_target + OIDC 发布的项目,应该立即检查:

如果这三个答案是"是"——你现在就应该修复它们。


参考来源:TanStack 官方 Postmortem · HN 613 分讨论

上一篇报道:TanStack npm 供应链攻击初始发现分析