2026 年 5 月 11 日 19:20–19:26 UTC,攻击者向 42 个 @tanstack/* npm 包发布 84 个恶意版本。攻击链组合利用了 GitHub Actions 的三个机制——pull_request_target Pwn Request、Actions 缓存投毒、以及从 runner 内存提取 OIDC token——在不窃取任何 npm token 的情况下完成了伪装发布。

恶意版本在约 20 分钟内被 StepSecurity 研究员 ashishkurmi 发现并公开报告。TanStack 已废弃全部受影响版本,npm 安全团队已介入移除 tarball。
影响范围
- 42 个包、84 个恶意版本(每个包两个版本,间隔约 6 分钟发布)
- 受影响:
@tanstack/router全系列子包 - 确认未受影响:
@tanstack/query*、@tanstack/table*、@tanstack/form*、@tanstack/virtual*、@tanstack/store、@tanstack/start(元包)
完整受影响包列表见 tracking issue #7383。
攻击链拆解
第一步:Pwn Request
2026-05-10 17:16 UTC,攻击者 fork TanStack/router 仓库,命名为 zblgg/configuration(刻意改名以规避 fork 列表搜索)。
次日 10:49 UTC,攻击者以 zblgg 身份向 TanStack/router 提交 PR #7378,标题"WIP: simplify history build"。
PR 中包含一个 3 万行的混淆 JS 文件 packages/history/vite_setup.mjs,提交者身份伪造为 [email protected]。
关键点:TanStack 的 bundle-size.yml 使用 pull_request_target 触发,PR 自动进入 CI 运行,无需维护者批准。而使用 pull_request 触发的 pr.yml 被首次贡献者审核机制正确拦截。
攻击者随后多次 force-push,每次都触发新的 pull_request_target 运行,反复执行恶意代码。
第二步:GitHub Actions 缓存投毒
恶意代码在 CI 运行期间向 pnpm store 目录写入污染数据。缓存 key 为 Linux-pnpm-store-${hashFiles('**/pnpm-lock.yaml')},与正式发布流程 release.yml 使用的完全一致。
当 benchmark-pr job 结束时,actions/cache@v5 的 post-step 将被污染的 pnpm store 保存到该 key。下一次 release.yml 在主分支运行时,Setup Tools 步骤会恢复这个被污染的缓存。
这是一个跨 fork↔base 信任边界的缓存投毒攻击。
第三步:OIDC Token 内存提取
release.yml 声明了 id-token: write(npm OIDC trusted publishing 需要)。当被污染的 pnpm store 在 runner 上恢复后,攻击者控制的二进制文件被调入执行:
- 通过
/proc/*/cmdline定位 GitHub Actions Runner.Worker 进程 - 读取
/proc/<pid>/maps和/proc/<pid>/mem导出 runner 内存 - 从内存中提取 OIDC token(runner 在
id-token: write设置时惰性生成) - 用该 token 直接 POST 到
registry.npmjs.org,绕过 workflow 的 Publish Packages 步骤
npm 收到的发布请求携带合法的 OIDC trusted-publisher binding(TanStack/router release.yml@refs/heads/main),因此通过了验证。但发布来自恶意代码在 test/cleanup 阶段的执行,而非正常的 Publish Packages 步骤。
恶意载荷分析
安装受影响版本后,npm install 解析恶意 optionalDependencies,从 fork 网络获取孤儿 payload commit,执行约 2.3MB 的混淆脚本 router_init.js。
该脚本具备三大能力:
凭据窃取: AWS IMDS / Secrets Manager、GCP metadata、Kubernetes service-account token、HashiCorp Vault token、~/.npmrc、GitHub token(环境变量、gh CLI、.git-credentials)、SSH 私钥。
加密外泄: 通过 Session/Oxen messenger 文件上传网络(filev2.getsession.org、seed{1,2,3}.getsession.org)外泄。端到端加密、无攻击者控制的 C2 服务器,只能通过域名/IP 级别网络阻断防御。
自我繁殖: 枚举受害者在 npm 上维护的其他包(registry.npmjs.org/-/v1/search?text=maintainer:<user>),自动注入并重新发布恶意版本。一个开发者中招,可能波及整个维护链。
时间线
| 时间 (UTC) | 事件 |
|---|---|
| 05-10 17:16 | 攻击者创建 fork zblgg/configuration |
| 05-10 23:29 | 恶意 commit 提交(伪造 claude 身份) |
| 05-11 ~10:49 | PR #7378 提交,触发 pull_request_target CI |
| 05-11 11:01–11:11 | 多次 force-push,反复触发 CI |
| 05-11 19:20:39 | 第一批恶意版本发布(42 个包) |
| 05-11 19:26 | 第二批恶意版本发布 |
| 05-11 ~19:40 | StepSecurity 研究员发现并报告 |
| 05-11 当天 | 全部版本废弃,npm 安全团队介入 |
响应与加固
TanStack 在事件后采取了以下措施:
- 废弃全部 84 个恶意版本,联系 npm 安全团队移除 tarball
- 清除被污染的 GitHub Actions 缓存条目
- 合并加固 PR(#6773):重构
bundle-size.yml,添加 repository-owner guard,pin 第三方 action 引用 - 发布完整 postmortem,公开时间线和技术细节
GitHub Security Advisory:GHSA-g7cv-rxg3-hmpx
对开源生态的启示
OIDC trusted-publishing 作为 npm 与 GitHub 的信任桥梁,一旦 Actions 层被攻破,整个发布链的信任模型断裂。这次事件暴露了几个结构性问题:
pull_request_target的信任边界需要更严格的隔离——fork 代码不应拥有写入 base 仓库缓存的权限- Actions 缓存跨 fork↔base 的共享机制是一个系统性风险点
- Runner 进程内存中 OIDC token 的安全性依赖 Linux
/proc权限模型,容器化环境并非天然安全 - 对
pull_request_target的防护和第三方 action 的 pin 策略,已成为开源项目 CI 加固的基本要求
据 Socket 追踪,这起事件属于 "Mini Shai-Hulud" 系列活动的一部分,已有多个 npm 和 PyPI 包受害。
自查建议: 如果你在 2026-05-11 当天安装过 @tanstack/router* 相关包,应将安装主机视为可能被入侵,轮换 AWS、GCP、Kubernetes、Vault、GitHub、npm、SSH 等全部可达凭据。
来源:TanStack Blog · Socket Blog