Postmortem: TanStack npm 供应链妥协事件分析

TL;DR · AI 摘要
TanStack 于 2026 年 5 月 11 日遭遇 npm 供应链攻击,攻击者利用 GitHub Actions 缓存投毒和 OIDC 令牌提取,在 42 个包中发布了 84 个恶意版本。
核心要点
- 攻击者利用 pull_request_target 模式和 GitHub Actions 缓存投毒,在 6 分钟内发布 84 个恶意版本。
- 未窃取 npm 令牌,而是通过运行时内存提取 Runner 进程中的 OIDC 令牌进行攻击。
- 安装受影响版本的用户必须立即轮换 AWS、GCP、Kubernetes、Vault、GitHub、npm 和 SSH 凭证。
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- TanStack npm Supply-Chain Compromise
- Attack Vector
- Pull Request Target Pattern
- GitHub Actions Cache Poisoning
- Impact Scope
- 84 Malicious Versions
- 42 Affected Packages
- Response Actions
- Version Deprecation
- Credential Rotation
金句 / Highlights
值得收藏与分享的关键句。
2026 年 5 月 11 日 19:20 至 19:26 UTC 期间,攻击者在 42 个 @tanstack/* npm 包中发布了 84 个恶意版本。
没有 npm 令牌被盗,npm 发布工作流本身也未受到损害。
我们强烈建议任何在 2026 年 5 月 11 日安装了受影响版本的人轮换 AWS、GCP、Kubernetes、Vault、GitHub、npm 和 SSH 凭证。
事后分析:TanStack npm 供应链攻击事件 | TanStack 博客

[](https://github.com/TanStack)[](https://x.com/tan_stack)[](https://bsky.app/profile/tanstack.com)[](https://instagram.com/tan_stack)[](https://youtube.com/@tan_stack)[](https://tlinz.com/discord)
搜索...
K
自动
博客本页内容
事后分析:TanStack npm 供应链攻击事件
复制页面
_作者:Tanner Linsley,2026年5月11日。_
最后更新: 2026-05-11
简要总结[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#tldr)
2026年5月11日UTC时间19:20至19:26期间,攻击者通过结合以下方式,在42个@tanstack/* npm包中发布了84个恶意版本:pull_request_target "Pwn Request" 模式、跨越fork↔base信任边界的GitHub Actions缓存投毒,以及从GitHub Actions运行器进程中提取OIDC令牌的运行时内存提取。没有npm令牌被盗,npm发布工作流本身也未被破坏。
恶意版本在20分钟内被stepsecurity公司的外部研究员ashishkurmi公开发现。所有受影响版本已被弃用;npm安全团队已介入从注册表中移除tarballs。我们没有证据表明npm凭证被盗,但我们强烈建议任何在2026年5月11日安装了受影响版本的用户轮换安装主机可访问的AWS、GCP、Kubernetes、Vault、GitHub、npm和SSH凭证。
跟踪问题:TanStack/router#7383GitHub安全公告:GHSA-g7cv-rxg3-hmpx
影响[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#impact)
受影响包[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#packages-affected)
42个包,84个版本(每个包两个版本,发布时间间隔约6分钟)。完整表格请参见跟踪问题。已确认安全的包系列:@tanstack/query*、@tanstack/table*、@tanstack/form*、@tanstack/virtual*、@tanstack/store、@tanstack/start(元包,不包括@tanstack/start-*)。
恶意软件行为[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#what-the-malware-does)
当开发者或CI环境对任何受影响版本运行npm install、pnpm install或yarn install时,npm会解析恶意的optionalDependencies条目,从fork网络中获取孤立的有效载荷提交,运行其prepare生命周期脚本,并执行被植入受影响tarball中的约2.3 MB混淆的router_init.js脚本。该脚本:
- 从常见位置收集凭证:AWS IMDS / Secrets Manager、GCP metadata、Kubernetes service-account tokens、Vault tokens、~/.npmrc、GitHub tokens(环境变量、gh CLI、.git-credentials)、SSH private keys
- 通过 Session/Oxen messenger 文件上传网络外泄数据(filev2.getsession.org, seed{1,2,3}.getsession.org)——端到端加密且无攻击者控制的 C2 通道,因此仅能通过 IP/域名阻止作为网络缓解措施
- 自我传播:通过 registry.npmjs.org/-/v1/search?text=maintainer:<user> 枚举受害者维护的其他包,并以相同注入方式重新发布
由于 payload 作为 npm install 生命周期的一部分运行,在 2026-05-11 安装了受影响版本的任何用户都必须将安装主机视为可能已被入侵。
- * *
时间线[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#timeline)
所有时间均为 UTC。时间戳源自 GitHub API 和 npm registry。
攻击前(缓存投毒阶段)[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#pre-attack-cache-poisoning-phase)
| 时间 | 事件 | | --- | --- | | 2026-05-10 17:16 | 攻击者创建 fork github.com/zblgg/configuration(TanStack/router 的 fork,故意重命名以规避 fork 列表搜索) | | 2026-05-10 23:29 | 恶意提交 65bf499d16a5e8d25ba95d69ec9790a6dd4a1f14 由伪造身份 claude <claude@users.noreply.github.com> 在 fork 上提交。添加 packages/history/vite_setup.mjs(约 30,000 行打包的 JS payload)。提交消息前缀 [skip ci] 以抑制推送事件的 CI | | 2026-05-11 ~10:49 | zblgg 针对 TanStack/router#main 提交 PR #7378,标题为 "WIP: simplify history build" | | 2026-05-11 10:49 起 | bundle-size.yml 和 labeler.yml(均为 pull_request_target)为 PR 自动运行——无需首次贡献者审批,因为 pull_request_target 绕过此关卡。pr.yml(使用 pull_request)未运行,因待审批而被阻止 | | 2026-05-11 11:01–11:11 | zblgg 对 PR 头部进行多次强制推送,每次均触发更多 pull_request_target 运行 | | 2026-05-11 11:11 | 强制推送将 65bf499d(恶意提交)落地到 PR 头部。bundle-size.yml 的 benchmark-pr 任务检出 refs/pull/7378/merge,运行 pnpm install + pnpm nx run @benchmarks/bundle-size:build —— 此过程执行 vite_setup.mjs | | 2026-05-11 11:29 | 缓存条目 Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11(1.1 GB)保存至 TanStack/router 的 GitHub Actions 缓存,作用域 refs/heads/main —— 键值设计匹配 release.yml 后续对 main 的推送查询 | | 2026-05-11 11:31 | 攻击者将 PR 强制推送回当前 main HEAD(b1c061af),使可见 PR 变为零文件空操作。PR 在同一分钟内关闭且分支删除。缓存投毒持续存在。 |
爆发(发布阶段)[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#detonation-publish-phase)
| 时间 | 事件 | | --- | --- | | 2026-05-11 19:15 | Manuel 合并 PR #7369(Shkumbin 的 CSS.supports 修复)→ 推送至 main 触发 release.yml。工作流运行 25613093674 启动(19:15:44),并失败。 | | 2026-05-11 19:20:39 | npm registry 接收 @tanstack/history@1.161.9 和 41 个关联包的发布(42 个包中约 84 个版本,但仅约半数显示此精确秒;其余在第二次运行期间发布)。发布通过 TanStack/router release.yml@refs/heads/main 的 OIDC trusted-publisher 绑定认证——但并非来自工作流定义的 Publish Packages 步骤(因测试失败被跳过),而是来自测试/清理阶段运行的恶意软件,该软件通过工作流的 id-token: write 权限生成 OIDC token 并直接 POST 至 registry.npmjs.org | | 2026-05-11 19:20:47 | 运行 25613093674 完成(状态:失败) | | 2026-05-11 19:16 | Manuel 合并 PR #7382(jiti tsconfig paths 修复)→ 第二次推送至 main 触发 release.yml | | 2026-05-11 19:16:22 | 工作流运行 25691781302 启动。恢复相同投毒缓存 | | 2026-05-11 19:26:14 | npm registry 接收第二批次包版本发布(@tanstack/history@1.161.12 等)。使用相同 OIDC 机制 | | 2026-05-11 19:26:20 | 运行 25691781302 完成(状态:失败) |
检测与响应[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#detection-and-response)
| 时间 | 事件 | | --- | --- | | 2026-05-11 ~19:50 | 外部研究人员(carlini)提交 issue #7383,完整描述恶意 optionalDependencies 指纹及包列表(初始 14 个,共 42 个) | | 2026-05-11 ~19:50 | 研究人员直接通知 npm 安全团队 | | 2026-05-11 ~20:00 | Manuel 在 #7383 中确认——事件响应启动 | | 2026-05-11 ~20:10 | Manuel 移除 GitHub 上所有其他团队推送权限,以防用户机器遭入侵 | | 2026-05-11 ~20:30 | Tanner 通过 [security@npmjs.com](mailto:security@npmjs.com) 发送邮件,附完整 IOC 列表及请求从 registry 端移除 tarballs。通过 npm 提交正式恶意软件报告 | | 2026-05-11 ~21:00 | 对全部 295 个 @tanstack/* 包的全面扫描确认影响范围:42 个包、84 个版本。Tanner 启动 npm 废弃流程处理所有 84 个受影响包。@tan_stack 及维护者通过 Twitter/X/LinkedIn/Bluesky 公开披露 | | 2026-05-11 21:30 | 调查确定 bundle-size.yml pull_request_target 缓存投毒向量及 zblgg/configuration fork。通过 API 清除所有 TanStack/* GitHub 仓库的缓存条目。合并加固 PR:重构 bundle-size.yml,添加 repository_owner 防护,第三方 action 引用固定至 SHA。发布 GitHub 官方安全公告,申请 CVE |
- * *
根本原因[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#root-cause)
三个漏洞相互串联。每个漏洞对攻击都是必要的;单独任何一个都不足以构成攻击。
3. 从运行器内存中提取 OIDC 令牌[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#3-oidc-token-extraction-from-runner-memory)
release.yml 声明了 id-token: write(这是 npm OIDC 可信发布所必需的)。当被污染的 pnpm 存储在运行器上恢复时,攻击者控制的二进制文件已存在于磁盘上,并在构建步骤中被调用。这些二进制文件:
- 通过 /proc/*/cmdline 定位 GitHub Actions Runner.Worker 进程
- 读取 /proc/<pid>/maps 和 /proc/<pid>/mem 以转储工作进程的内存
- 提取 OIDC 令牌(当设置 id-token: write 时,运行器会在内存中惰性生成该令牌)
- 使用该令牌直接向 registry.npmjs.org 进行 POST 请求认证——完全绕过工作流的发布包步骤
这与 2025 年 3 月 tj-actions/changed-files 被入侵事件中使用的内存提取技术(包括逐字复制的 Python 脚本,并附有归属注释)相同。攻击者并未发明新的攻击手段;他们只是重新组合了已发表的研究成果。
为何单一漏洞不足为患[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#why-none-alone-is-enough)
- 单独使用 pull_request_target 对于可信操作(如添加标签、评论)是安全的
- 单独的缓存投毒(例如,来自已受损依赖项内部)需要一个独立的发布渠道
- 单独的 OIDC 令牌提取需要在运行器上已存在代码执行
这一链条之所以有效,是因为每个漏洞都跨越了其他漏洞所假设的信任边界:PR 分支代码进入基础仓库缓存、基础仓库缓存进入发布工作流运行时、以及发布工作流运行时进入 npm 注册表写入访问。
- * *
检测[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#detection)
我们如何发现[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#how-we-found-out)
检测来自外部。carlini 在发布后约 20 分钟打开了 issue #7383,并附有完整的技术分析。Tanner 在启动战情室后不久就接到了 Socket.dev 的电话,确认了情况。
IOC 指纹(供下游维护者和安全工具使用)[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#ioc-fingerprints-for-downstream-maintainers-and-security-tools)
在任意 @tanstack/* 包的清单文件中:
json
"optionalDependencies": {
"@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
}"optionalDependencies": {
"@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
}- 文件:router_init.js(约 2.3 MB,包根目录,不在 "files" 中)
- 缓存键:Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11
- 第二阶段有效载荷 URL:https://litter.catbox.moe/h8nc9u.js, https://litter.catbox.moe/7rrc6l.mjs
- 数据泄露网络:filev2.getsession.org, seed{1,2,3}.getsession.org
- 伪造的提交身份:claude <claude@users.noreply.github.com>(注意:非真实 Anthropic Claude——伪造的 GitHub no-reply 邮箱)
- 真实攻击者账户:zblgg(ID 127806521)、voicproducoes(ID 269549300)
- 攻击者分支:github.com/zblgg/configuration(TanStack/router 的分支,重命名以规避分支搜索)
- 孤立的有效载荷提交(在分支网络中):79ac49eedf774dd4b0cfa308722bc463cfe5885c
- 执行恶意发布的 workflow runs:
- github.com/TanStack/router/actions/runs/25613093674(第 4 次尝试)
- github.com/TanStack/router/actions/runs/25691781302
- * *
经验教训[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#lessons-learned)
做得好的方面[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#what-went-well)
- 外部研究人员在事件发生后约 20 分钟内发现并报告了完整技术细节
- 维护者团队立即在多个时区高效协调响应
- 检测社区几小时内已建立明确的公开 IOC 模式
可以改进的方面[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#what-could-have-been-better)
- 无内部告警机制。我们通过第三方得知此次入侵,需加强对自身发布的监控。我们将与生态系统中能快速检测此类问题的安全研究公司紧密合作(甚至可能内部实施),并进一步缩短反馈周期
- pull_request_target 工作流未经过审计,尽管这是长期已知的危险模式
- 第三方操作的浮动引用(@v6.0.2、@main)独立于本次事件,持续存在供应链风险
- 因 npm 的“存在依赖项则禁止取消发布”策略,几乎所有受影响包均无法取消发布。我们只能依赖 npm 安全团队从服务器端移除恶意 tar 包,导致数小时延迟(此期间恶意包仍可安装)
- npm 作用域的 7 位维护者列表意味着相同影响范围内存在七个独立的凭证窃取目标
- OIDC 可信发布者绑定缺乏单次发布审核。配置后,工作流中任意代码路径均可生成可发布令牌。需采取以下措施之一:(a) 改用需手动审核的短期经典令牌,或 (b) 增加来源验证以检测异常工作流步骤的发布
我们幸运的方面[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#what-we-got-lucky-on)
- 攻击者选择的有效载荷破坏了测试,导致发布步骤(本应生成更干净的 tar 包)被跳过——使攻击足够明显而快速暴露。若攻击者更谨慎(不破坏测试),可能延长数小时静默发布
- 攻击者直接复用公开战术(含归属注释的内存转储脚本),而非编写新代码——加速了 IOC 匹配过程
- * *
未解决问题[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#open-questions)
在完成事后分析前需解答以下问题:
- bundle-size.yml 的 Setup Tools 步骤是否实际调用了 actions/cache@v5?通过查看 PR #7378 的 pull_request_target 运行作业后日志验证(如运行 ID 25666610798)。Tanner 有访问权限,需手动操作
- 初始 PR 头提交(强制推送覆盖前)内容为何?GitHub reflog 可能存有记录,可通过 gh api 或 GitHub 支持团队查询
- 恶意提交如何具体进入分支的 git 对象存储——是通过 git 直接推送,还是 GitHub Web UI 创建(后者会留下审计日志)?
- voicproducoes 是真实账户还是傀儡账户?需交叉验证其活动历史
- npm 缓存是否也被污染(6 个重复的 linux-npm-store-* 条目)?是否有实际使用?
- 该攻击是否必须依赖 Nx Cloud,还是仅 GitHub Actions 缓存即可实现?
- 能否在 TanStack/router 分支网络中识别其他包含孤立有效载荷提交的分支?(若存在,清理难度更大——每个托管分支均通过 github:tanstack/router#79ac49ee... 保持可访问)
- 其他 TanStack 仓库(router、query、table、form、virtual 等)是否使用相同 bundle-size.yml 风格模式?需进行审计
- 发布窗口期内实际下载受影响版本的用户数量?需从 npm 支持获取数据
- 列出的七位维护者中,是否有任何人的机器被单独入侵?(恶意发布均未使用维护者 npm 令牌,但维护者机器可能通过自传播逻辑成为次要目标)
- * *
参考资料[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#references)
- 跟踪问题: TanStack/router#7383
- GitHub安全公告: GHSA-g7cv-rxg3-hmpx
- 相关研究:
- Adnan Khan, "构建缓存中的怪物:GitHub Actions缓存中毒"(2024年5月)— adnanthekhan.com
- GitHub安全实验室, "确保您的GitHub Actions和工作流安全:防止pwn请求" — securitylab.github.com
- StepSecurity, "Harden-Runner检测:tj-actions/changed-files动作已被攻破"(2025年3月)— stepsecurity.io
- * *
附录A — 受影响版本[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#appendix-a--affected-versions)
有关受影响版本的完整列表,请参阅GitHub安全公告: GHSA-g7cv-rxg3-hmpx
本页内容
金牌

银牌
[](https://ag-grid.com/react-data-grid/?utm_source=reacttable&utm_campaign=githubreacttable)[](https://netlify.com/?utm_source=tanstack)](https://workos.com/?utm_source=tanstack)
铜牌
[](https://sentry.io/?utm_source=tanstack)[](https://www.unkey.com/?utm_source=tanstack)