Postmortem: TanStack npm supply-chain compromise
2026年5月11日TanStack npm软件包安全事件总结
事件概要: 2026年5月11日(UTC时间19:20-19:26),一名攻击者利用“Pwn Request”模式、GitHub Actions缓存中毒以及运行时内存提取OIDC Token等方式,在42个@tanstack/* npm软件包的84个版本中植入了恶意代码。攻击者未窃取npm令牌,且npm发布工作流本身未受损。
检测与响应: 该恶意版本在公开检测后20分钟内,由外部研究员Ashish Kurmi(StepSecurity)发现。所有受影响的版本已被弃用,npm安全团队正在从注册中心删除相关tarball。虽然未发现npm凭据被盗,但强烈建议所有在2026年5月11日安装了受影响版本的用户,立即轮换AWS、GCP、Kubernetes、Vault、GitHub、npm和SSH凭据。
跟踪信息:
- 跟踪 issue: TanStack/router#7383
- GitHub 安全建议: GHSA-g7cv-rxg3-hmpx
受影响的软件包:
42个软件包,84个版本(每个软件包两个版本,间隔约6分钟发布)。具体列表请参考跟踪 issue。未受影响的软件包系列包括:@tanstack/query*, @tanstack/table*, @tanstack/form*, @tanstack/virtual*, @tanstack/store 和 @tanstack/start (元包,而非@tanstack/start-*)。
恶意软件的功能:
当开发者或CI环境安装受影响的版本时,npm会解析恶意optionalDependencies条目,从分叉网络拉取孤立payload提交,运行其prepare生命周期脚本,并执行一个被混淆的router_init.js文件(约2.3MB),该文件被偷偷地嵌入到受影响的tarball中。该脚本执行以下操作:
- 从常见位置收集凭据:AWS IMDS/Secrets Manager、GCP元数据、Kubernetes服务帐户令牌、Vault令牌、~/.npmrc、GitHub令牌(环境变量、gh CLI、.git-credentials)、SSH私钥。
- 通过Session/Oxen文件上传网络(filev2.getsession.org, seed{1,2,3}.getsession.org)进行数据外泄(端到端加密,攻击者无法控制C2,因此仅通过IP/域名阻止可以进行网络缓解)。
- 自我传播:枚举受害者维护的其他软件包(通过registry.npmjs.org/-/v1/search?text=maintainer:
),并以相同的注入方式重新发布它们。
攻击流程:
攻击者通过以下步骤完成攻击:
预攻击(缓存中毒阶段):
- 创建分叉仓库 github.com/zblgg/configuration (TanStack/router 的分叉,故意重命名以避免分叉列表搜索)。
- 在分叉仓库中提交恶意代码(包含约30,000行的
vite_setup.mjspayload)。 - 打开PR #7378提交到TanStack/router,标题为“WIP: simplify history build”。
- 利用
pull_request_target触发bundle-size.yml和labeler.yml自动运行,绕过首次贡献者批准。 - 通过多次强制推送,将恶意代码提交到PR头部。
benchmark-pr任务运行pnpm install + pnpm nx run @benchmarks/bundle-size:build,从而执行vite_setup.mjs。- 将包含恶意代码的缓存条目保存到GitHub Actions缓存中。
- 再次强制推送PR到当前HEAD,使可见的PR变为0文件的无操作。
爆发(发布阶段):
- Manuel合并PR #7369和#7382,触发release.yml。
- npm注册中心接收到受感染版本(@tanstack/history@1.161.9及其他41个软件包)的发布。