T
traeai
登录
返回首页
Hacker News Best

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

9.5Score
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 凭证。

结构提纲

按章节快速跳转。

  1. 2026 年 5 月 11 日,攻击者通过受污染的 CI/CD 管道在 42 个 TanStack 包中发布了 84 个恶意版本。

  2. 该漏洞结合了 pull_request_target 模式与 GitHub Actions 缓存投毒,从 Runner 内存中提取 OIDC 令牌。

  3. 恶意 tarball 在外部安全研究人员检测前的 20 分钟内被推送到注册表。

  4. 受影响版本已弃用,建议用户轮换所有可从安装主机访问的云平台和凭证。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 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 个恶意版本。

    摘要部分

    ⬇︎ 下载 PNG𝕏 分享到 X
  • 没有 npm 令牌被盗,npm 发布工作流本身也未受到损害。

    摘要部分

    ⬇︎ 下载 PNG𝕏 分享到 X
  • 我们强烈建议任何在 2026 年 5 月 11 日安装了受影响版本的人轮换 AWS、GCP、Kubernetes、Vault、GitHub、npm 和 SSH 凭证。

    摘要部分

    ⬇︎ 下载 PNG𝕏 分享到 X
#安全#供应链#npm#GitHub Actions#TanStack
打开原文

事后分析:TanStack npm 供应链攻击事件 | TanStack 博客

![Image 1Image 2Image 3 TanStack](https://tanstack.com/)

[](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

自动

登录

Start RC

Start RC

Router

Router

Query

Query

Table

Table

DB beta

DB beta

AI alpha

AI alpha

Form new

Form new

Virtual

Virtual

Pacer beta

Pacer beta

Hotkeys alpha

Hotkeys alpha

Store alpha

Store alpha

Devtools alpha

Devtools alpha

CLI alpha

CLI alpha

Intent alpha

Intent alpha

更多库

更多库

Builder Alpha

Builder Alpha

博客

博客

维护者

维护者

合作伙伴

合作伙伴

案例展示

案例展示

学习 NEW

学习 NEW

统计

统计

YouTube

YouTube

Discord

Discord

周边商品

周边商品

支持

支持

GitHub

GitHub

理念

理念

原则

原则

品牌指南

品牌指南

博客本页内容

事后分析: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

code
"optionalDependencies": {
  "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
}
code
"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)

  • * *

附录A — 受影响版本[#](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem#appendix-a--affected-versions)

有关受影响版本的完整列表,请参阅GitHub安全公告: GHSA-g7cv-rxg3-hmpx

在GitHub上编辑

本页内容

合作伙伴成为合作伙伴

金牌

![图片4: CodeRabbit图片5: CodeRabbit](https://coderabbit.link/tanstack?utm_source=tanstack&via=tanstack)![图片6: Cloudflare图片7: Cloudflare](https://www.cloudflare.com/?utm_source=tanstack)

银牌

[![图片8: AG Grid](blob:http://localhost/e1a96eded451fe5afb31b85553d97c14)![图片9: AG Grid](blob:http://localhost/4383c5eabbf0d845a08f654bc36ec5a0)](https://ag-grid.com/react-data-grid/?utm_source=reacttable&utm_campaign=githubreacttable)![图片10: SerpAPI图片11: SerpAPI](https://serpapi.com/?utm_source=tanstack)![图片12: OpenRouter图片13: OpenRouter](https://openrouter.ai/?utm_source=tanstack)[![图片14: Netlify](blob:http://localhost/7969df8e4856c980e3ec33e810b2fdd3)![图片15: Netlify](blob:http://localhost/24d73375638b043256608924f69862f0)](https://netlify.com/?utm_source=tanstack)![图片16: WorkOS![图片17: WorkOS](blob:http://localhost/63ab9198a3814086bee58577222efe05)](https://workos.com/?utm_source=tanstack)![图片18: Clerk图片19: Clerk](https://go.clerk.com/wOwHtuJ)

铜牌

![图片20: Electric图片21: Electric](https://electric-sql.com/)[![图片22: Sentry](blob:http://localhost/2ec396d44617754f9899dbf8f84e8284)![图片23: Sentry](blob:http://localhost/fa9092f27899d80931754571c5f0d3a2)](https://sentry.io/?utm_source=tanstack)![图片24: Railway图片25: Railway](https://railway.com/?utm_medium=sponsor&utm_source=oss&utm_campaign=tanstack)![图片26: PowerSync图片27: PowerSync](https://powersync.com/?utm_source=tanstack&utm_campaign=tanstack_partner)![图片28: Prisma图片29: Prisma](https://www.prisma.io/?utm_source=tanstack&via=tanstack)![图片30: Strapi图片31: Strapi](https://strapi.link/tanstack-start)[![图片32: Unkey](blob:http://localhost/58850309d06dec25049a27f46f477723)![图片33: Unkey](blob:http://localhost/1f4ec7445816c68b361670da8b85b070)](https://www.unkey.com/?utm_source=tanstack)

StartRouterQueryTableFormDBAIIntent

AI 可能会生成不准确的信息,请核实重要内容