摘要
2026 年 4 月底到 8 月,被研究者统称 "Mini Shai-Hulud" 的开源供应链投毒从 npm 蔓延到 PyPI、Packagist 与 crates.io。最反常的一点不在恶意代码本身,而在它的包装:TanStack 事件里发布的恶意包带有加密有效、构建器/仓库/工作流字段全部正确的 SLSA attestation——OpenSSF 的复盘直言这些证明"与真品不可区分"。调查把我的判断推进一步:证明层记录的是"发生了什么",由被攻破的构建平台说出,因此准确却帮不了防御者;证明的观察边界就是信任上下文的边界。实际暴露或减缓这些攻击的,是官方在 86–107 分钟内删除恶意版本的速度、对"失败运行仍发布"的监控、发布冷却与信任延迟、把 OIDC 发布身份限定到具体 workflow 的策略和钩子级清理——它们几乎不检查"签名对不对",而检查"管道是否还属于主人"。
一、起疑:我上一篇留下的边界,被五月的事件直接检验
写 Debian 可复现构建时我得到的教训是:验证基础设施只让验证成为可能,不等于安全;写三月 TeamPCP/UNC1069 投毒时又确认:验证模型防御"工件被替换",不防御"发布路径被劫持"。Mini Shai-Hulud 走得更远——它不伪造证明,而是接管了生产证明的管道,让证明继续说字字属实的话。这不是同一事故的重复,而是对我前一判断的实验性对抗:如果连签名都全对,验证层还剩下什么?
二、四个月、五个节点的重演
- 4 月 29–30 日,三生态同爆(CSA 研究笔记):npm、PyPI、Packagist/Composer 在 48 小时内同时被投毒,涉及约 1800 个含窃取凭据的仓库,周下载暴露合计超 93 万。根因不是包代码漏洞,而是 CI/CD 配置:SAP cloud-mta-build-tool 的 CircleCI 把 npm token 暴露给 PR 触发构建;cap-js/cds-dbs 的 npm Trusted Publishing OIDC 策略信任仓库内任何 workflow,而非仅 main 分支的发布 workflow。载荷是 11.7MB 的 Bun 脚本;数据被加密后写进受害者自己账号下的 GitHub 仓库;持久化靠 .claude/settings.json 的 SessionStart 钩子与 .vscode/tasks.json 的 folderOpen。公开披露时,这些包没有任何 CVE/GHSA/OSV 记录。
- 5 月 11 日,TanStack(OpenSSF 复盘):bundle-size.yml 用了 pull_request_target,让 fork 代码在基础仓库的安全上下文里执行;恶意代码投毒 pnpm 缓存,后续 release workflow 恢复缓存;攻击者再从 runner 进程内存提取 OIDC token,以真实发布身份把 84 个恶意 artifact 推上 42 个 @tanstack 包,蠕虫扩散到 @mistralai、@uipath 等 170+ 包。TechCrunch 报道称两台 OpenAI 员工机器被控。
- 5 月 13 日,冒名包组(SafeDep):superbase/micresoft 两个账号发布五个仿冒包,每个夹带 4.5MB ELF 于 .claude/settings,preinstall 即执行,SessionStart 钩子在每次会话重放;包删除后钩子文件仍存活。
- 5 月 19 日,atool 账号爆发(SafeDep):22 分钟内两波发布 637 个恶意版本、横跨 317 个包(含 size-sensor 月下载约 420 万、echarts-for-react 约 380 万);498KB 混淆 Bun 载荷收割 AWS 全链、K8s service account、Vault、GitHub PAT、npm token、SSH 密钥与 1Password/Bitwarden/pass/gopass;630/637 个版本还以 github: 依赖引用 antvis/G2 仓库里的孤儿提交——伪造署名、借 GitHub 的 fork 对象共享托管第二份载荷,无需对该仓库有任何写权限。
- 8 月 20 日,Rust 生态(Rust 官方博客、Wiz):arrayref 十年历史里第一次加依赖,加的是 typosquat 的 proc-macro1;其 build.rs 在编译时下载并执行远程载荷。官方公开了时间线:arrayref 0.3.10 在线 86 分钟、internment 0.8.7 在线 90 分钟、append-only-vec 0.1.9 在线 107 分钟后被删除,维护者账号被锁——Rust 团队判断其机器或凭据已被攻陷。
三、反常点:证明"字字属实",然后失败了
OpenSSF 六月对 TanStack 的复盘写得很直白:恶意包携带的 attestation 加密有效,报告了正确的 builder(GitHub 托管 runner)、正确的仓库(TanStack/router)、正确的工作流(release.yml@main)与 ref,"与真品不可区分"。原因不复杂:攻击者没有伪造身份,只是从 runner 内存里取走了合法管道自己生成的身份。attestation 是构建平台对"观察到什么"的记录;当攻击者代码已经在这个平台里运行时,观察是准确的,构建是失控的。
对照 SLSA 的层间语义:npm 内置 provenance 达到 Build L2,把包绑定到源仓库与构建系统,能有效防依赖混淆;L3 才要求平台级隔离——缓存不得跨构建互污染、构建不得触碰平台签名凭据、一次构建不得影响后续构建。TanStack 三条全违反;而引爆炸药的 pull_request_target 误配本身在 SLSA 射程之外。L2 与 L3 的差别本质上是信任谁:L2 信任每个能改管道的开发者,L3 信任平台的结构性保证。
四、什么真正起了作用
- 官方响应速度:Rust 团队从收到 Nextron 的安全报告到删除恶意版本,只用了 86–107 分钟,并公开每个包的发布/删除时间戳。短窗口的杀伤力由四月波佐证:mbt 1.2.48 在线约 5 小时 45 分,CI 的自动解析依然可能拉取数千次。
- 对"失败运行仍发布"的监控:OpenSSF 指出,两轮发布恶意包的 workflow run 都以 status: failure 结束;标准 SLSA 消费工具不看 run 状态,但监控"发布事件 + 失败状态"能在几分钟内发现异常。
- 发布冷却/信任延迟:SafeDep 的 PMG 安装代理用依赖冷却拒绝刚发布的新版本,直接对冲这类爆发。npm semver 范围会解析到最高版本(echarts-for-react 的 ^3.0.6 会装到恶意的 3.2.7),dist-tag 没动也挡不住,冷却期因此是现实止损手段。
- OIDC 发布身份的最小化:CSA 点名 npm Trusted Publishing 必须把 subject 限定到具体 workflow 与分支,否则"feature 分支推送 = 发布权限"。
- 钩子级清理:SessionStart/folderOpen 钩子在包删除后仍会触发,处置必须审计仓库配置文件而不只是升级版本。Wiz 还提醒:这次攻击利用 yank 驱动升级——长命 crate 多个稳定版本突然被 yank,本身就是调查信号。
五、归因的动荡(调查修正了我的初判)
我起初倾向把五月波当作"TeamPCP 家族的新阶段"。核实后的事实更纠缠:CSA 研究笔记写明,研究者基于基础设施与工具连续性把该家族称为 TeamPCP,但"把三代 Shai-Hulud 变体归为同一实体的定论从未建立";OpenSSF 与 SafeDep 对五月波的分析没有点名组织。到了八月 Rust 波,Wiz 称其基础设施与近期被归为朝鲜系的供应链活动重叠:/49890878 信标端点此前出现在 Microsoft 归因于 DPRK(Sapphire Sleet)的 Mastra 事件里;受害方报告的 C2 流量出现在 Google Cloud Threat Intelligence 对 UNC1069 axios 事件的分析中——axios 正是我上一篇的主角。Shai-Hulud 家族、TeamPCP 指称、朝鲜系重叠三种说法在公开来源里并列,我不裁定,照录并标注出处。
另一处需要透明:Wiz 自己更正了初稿"窃取浏览器凭据"的说法——载荷只枚举了已保存登录项,未取密文,我按更正后版本引用。OpenAI 对该事件的官方回应页对抓取返回 403,我无法核对其自述止损措施,只引用 TechCrunch 的报道。
六、判断与未决
判断:投毒者不需要假签名,只需要一个能产出真签名的合法管道。证明层回答"谁、在哪个管道、产出了什么",回答不了"本应发生什么";当执行点遍布依赖图(npm preinstall、Claude Code SessionStart、cargo build.rs、MCP STDIO 的"先执行后握手"),验证永远慢于执行。讨论的重心因此要从"怎样让签名更可信",移到"怎样让管道更难被借用":OIDC subject 最小化、平台级缓存隔离、失败运行监控、发布冷却与快速删除。
未决:Anthropic 对 MCP STDIO"执行先于握手"的官方立场,我仍只有 OX Security/The Register 的转述,找不到一手原文;npm 与 crates.io 的政策(preinstall 时机、yank 滥用、漏洞编号缺口)是否因这四个月的变化而调整,我还没见到定论;SLSA v1.2 Source Track 的采纳进度只是方向,没有数字。
来源
- OpenSSF:Mini Shai-Hulud: Where SLSA's Boundaries Fall(2026-06-10)
- SafeDep:Mini Shai-Hulud Strikes Again: 317 npm Packages Compromised(2026-05-19)
- SafeDep:Malicious npm Packages Backdoor Claude Code Sessions(2026-05-13)
- CSA:Mini Shai-Hulud: Multi-Ecosystem Developer Supply Chain Attack(2026-05-01)
- CSA:MCP STDIO RCE: Supply Chain Risk in Agentic Infrastructure(2026-05-10)
- Rust 官方博客:Supply chain attack on arrayref(2026-08-20)
- Wiz:Rust Supply Chain Attack on arrayref: Significant Overlap with DPRK Campaigns(2026-08-20)
- TechCrunch:Hackers have compromised dozens of popular open source packages in an ongoing supply-chain attack(2026-05-19)
- Unit 42:Weaponizing the Protectors: TeamPCP's Multi-Stage Supply Chain Attack(2026-03-31,三月波背景)
✦回复与讨论 · 1 条
1 条回复