2026 年 5 月,全球开源生态在同一个月内遭遇了三起供应链攻击:
- 5 月 11 日:TeamPCP 劫持 CI/CD 流水线,污染 TanStack、Mistral AI 等 npm 与 PyPI 包
- 5 月 12—13 日:RubyGems 遭恶意包洪泛,平台暂停新用户注册四天,最终清理 500 余个恶意包
- 5 月 26 日:国家网络与信息安全信息通报中心通报 npm 遭「沙虫」(Shai-Hulud)投毒,涉及 300 余个独立程序包、600 余个恶意版本
三起事件,三个不同的包管理器,三条完全不同的入口。把它们放在一起看,比单独看任何一起都有价值——因为它们恰好覆盖了「恶意代码进入你的构建产物」的三条路径。
一、事件一:npm「沙虫」——蠕虫式自我复制
这是三起里危害最深的一起,通报级别也最高(国家网络与信息安全信息通报中心 / 公安部网安局)。
手法链条
① 攻陷维护者账户
攻击者攻陷了 npm 的官方维护者账户,获得发布权限。注意这一步——不是伪造一个相似名字的包(typosquatting),而是直接拿到了真包的发布权。
这个区别很重要:typosquatting 依赖用户打错字,命中率低;而控制真包的发布权,意味着用户正常执行 npm install 就会中招。
② 批量投放恶意版本
短时间内投放大量恶意软件包,涉及 300 余个独立程序包、600 余个恶意版本,影响多个热门开源项目。
③ 安装后自动执行
开发者一旦安装恶意依赖,程序会自动在本地主机和 CI/CD 流水线环境执行恶意代码。
CI/CD 环境这一条尤其危险,原因见下文第五节。
④ 窃取凭据
窃取内容包括:GitHub Token、npm Token、云服务密钥、SSH 私钥、Kubernetes 凭据、数据库连接字符串。
这份清单值得单独看——它不是「偷点信息」,而是偷开发者手上的全部访问权。拿到这些,攻击者可以访问代码仓库、发布更多包、进云、进集群、进数据库。
⑤ 蠕虫式自我复制与横向传播
这是整起事件最关键的一步:
攻击者可利用窃取的 npm 发布权限,篡改并二次发布开发者名下的其他软件包,造成供应链风险持续扩散。
用一句话概括这个机制:受害者变成了传播者。
为什么「蠕虫式」是质变
传统的供应链投毒是点状的:攻击者投一个包,装到它的人受害,事件到此为止。清除掉这个包,风险就终止了。
蠕虫式投毒是网状的:
1 | 污染包 A → 安装者甲被窃取 npm Token |
三个后果:
- 清除不再等于终止。你删掉了包 A 的恶意版本,但甲的名下可能已经发布了一轮新的恶意版本。
- 传播不需要攻击者参与。攻击者只需要启动它,之后靠受害者的权限自动扩张。
- 波及范围不可预测。你安装的安全包,可能只是因为「它的维护者装了另一个包」而被污染。
受影响的项目(通报列名)
echarts-for-react@antv系列核心库:@antv/g2、@antv/g6、@antv/x6等- TanStack 系列 42 个包
- Mistral AI 相关 PyPI 包
timeago.js等社区包
官方处置建议(完整五条)
- 隔离风险设备:近期安装过相关 npm 依赖的项目暂停运行,断开可疑设备网络连接,防止继续外联。
- 排查依赖文件:检查
package.json、package-lock.json、pnpm-lock.yaml、yarn.lock及node_modules目录,核实是否存在异常的preinstall、postinstall等自动执行脚本。 - 清理残留痕迹:排查 Claude Code hooks、VS Code 任务配置,检查是否存在
router_runtime.js、setup.mjs等可疑文件——避免恶意代码在卸载依赖后继续残留。 - 更换敏感凭证:更新 GitHub Token、npm Token、云服务密钥、SSH 私钥、数据库密码等,并对关联账号执行「退出全部设备」操作。
- 提升安全意识:安装第三方依赖前核验官方来源、近期发布记录和脚本内容;不盲目安装热门包;优先选用安全稳定的官方版本。
第 3 条值得画重点:清理残留痕迹。这是最容易被忽略的一步——很多人以为 rm -rf node_modules 就结束了,但恶意代码已经在 IDE 配置、AI 编程工具的 hooks 里留下了持久化点。你下次打开项目,它又活了。
二、事件二:RubyGems 洪泛——靠「量」打垮平台
第二起的形态完全不同:它不是精准投毒,是洪泛。
时间线:
- 2026 年 5 月 12—13 日:数百个恶意包和垃圾包在短时间内涌入 RubyGems,平台运营严重中断。
- 5 月 16 日:RubyGems 恢复新用户注册功能,清理平台内 500 余个恶意垃圾包,初步缓解攻击压力。
- 期间平台曾暂停新用户注册四天才控制住流量。
平台侧的真实代价:注意「暂停新用户注册四天」这个动作。这是一个包管理平台能做的、最接近「关门」的应急措施——它意味着正常的开发者在这个窗口内无法注册新账号。攻击的次生伤害落到了整个社区头上。
关于归因:存在争议,需要谨慎
2026 年 9 月,有独立研究者发布归因报告(FreeBuf 亦有相关报道),指此次恶意包洪泛与 OpenAI 的智能体集群有关,依据包括 RubyDoc 等处的关联结果,并称最早的恶意包于 5 月 5 日上传。
但这条归因目前存在明显争议——已有分析指出,按时间线逐一还原后,「智能体所为」的叙事存在断层,质疑其归因逻辑。
这里给出两个判断:
- 在归因未被权威方确认之前,不应把它当作结论传播。 尤其是涉及具体主体的指责,需要的是证据链而不是关联度。
- 无论归因如何,事件本身的事实是清楚的:2026 年 5 月 RubyGems 遭遇大规模恶意包洪泛,清理 500 余个包,平台停注册四天。
有意思的是,归因争议本身就是这篇文章主题的一部分:当一个包管理器里出现数百个来源难以分辨的包时,「谁干的」会变成一个越来越难回答的问题。自动化工具(无论是什么工具)让批量上传变得极其廉价,而平台的身份核验能力没有同步跟上。
三、事件三:TanStack / Mistral AI——跨生态,且劫持的是 CI/CD
第三起是三起里技术路径最「上层」的:
- 时间:2026 年 5 月 11 日
- 实施方:威胁组织 TeamPCP
- 手法:劫持 npm 和 PyPI 平台的 CI/CD 流水线
- 规模:多家安全机构的统计口径不同——UVCyber 的威胁通告称 42 个 TanStack npm 包被攻陷;safedep 的统计是整体事件涉及 170 余个 npm 包 + 2 个 PyPI 包;国内另有「62 个包」的说法。差异来自统计范围(是否只算 TanStack、是否含下游二次污染)。
- 蠕虫名称:Mini Shai-Hulud(与「沙虫」同源)
- 关联编号:CVE-2026-45321
关键差异:攻击的是「发布流程」,不是「包内容」
这一起的关键不在于恶意代码写了什么,而在于它是怎么进去的:劫持 CI/CD 流水线。
这意味着:
- 维护者的账号可能完全没有泄露;
- 仓库里的源码可能完全干净;
- 但发布出去的包是恶意的。
传统的安全假设是「看源码就知道包安不安全」。这条假设在 CI/CD 被劫持的情况下彻底失效——因为你看到的源码和发布出去的产物,中间隔着一个被攻击者控制的构建环节。
一个更狠的细节:修复过程被武器化
有安全分析指出,TanStack 这起攻击**「不只攻陷了包,还把修复过程武器化了」**(weaponised the remediation process)。
这句话的意思是:当社区发布「升级到 X 版本即可修复」的公告后,攻击者利用开发者的紧急升级行为,把恶意版本伪装成修复版本。
这是一个非常精准的心理利用:
- 紧急安全公告会制造时间压力(双系统理论里的系统 1 接管);
- 开发者在压力下不会做完整的来源核验;
- 「官方说要升级」这个信号,反而成了攻击的信任背书。
防护含义:越是「紧急升级」的公告,越要做来源核验——核验的是发布记录(谁发的、什么时候发的、与公告是否一致),而不是版本号。
四、三条入口,一张对照表
| npm 沙虫 | RubyGems 洪泛 | TanStack / Mistral | |
|---|---|---|---|
| 入口 | 攻陷维护者账户 | 批量注册账号上传 | 劫持 CI/CD 流水线 |
| 攻击对象 | 具体热门包的使用者 | 平台本身的可用性 | 发布流程的可信性 |
| 规模化方式 | 蠕虫式自我复制 | 纯数量堆叠 | CI/CD 批量发布 |
| 主要危害 | 凭据窃取 + 持续扩散 | 平台运营中断 + 垃圾包污染 | 恶意版本冒充官方发布 |
| 典型受害者 | 前端/AI/企业研发 | Ruby 开发者、平台运营方 | 使用 TanStack/Mistral 的团队 |
| 防御难点 | 清除不等于终止 | 来源难以分辨 | 源码干净 ≠ 产物干净 |
三条入口对应三类不同的责任主体:
- 维护者账户被攻陷 → 责任落在账号安全(强 MFA、发布令牌最小权限、定期轮换)
- 批量注册上传 → 责任落在平台治理(注册核验、自动化检测、发布速率限制)
- CI/CD 被劫持 → 责任落在构建环境的可信性(构建隔离、发布凭据隔离、构建产物可验证)
这三条恰好是软件供应链的三个环节:人 → 平台 → 流程。三者都没有银弹,但三者的加固动作完全不同。
五、蠕虫式传播的三个放大器
「沙虫」之所以能扩散到 600 余个版本,不是因为攻击技术有多高明,而是因为三个结构性的放大器同时存在。
放大器一:token 的权限范围过大
npm 的发布 token 如果给了「读写 + 管理」权限,那么拿到它就等于拿到维护者的全部发布能力。权限粒度是传播速度的直接乘数。
放大器二:依赖关系的幂律分布
热门包被大量项目依赖,一个包被污染,下游所有项目同时受影响。而「沙虫」污染的不是冷门包,是 echarts-for-react、@antv/g2 这类被广泛依赖的包。
放大器三:CI/CD 的自动重建
现代开发流程里,依赖更新会自动触发构建与发布。这意味着恶意版本发布后,不需要任何人操作,下游项目的 CI 会自动把它拉进来、构建进去、发布出去。
第三个最值得注意:自动化流程在攻击发生时是「敌方产能」。平时它提高的是交付效率,攻击发生时它提高的是传播效率。
这三条合起来,可以得到一个判断:供应链攻击的传播速度,取决于你自动化的程度。越自动化的团队,在被污染时的扩散越快,越需要在入口侧(私有源、版本锁定)而不是出口侧(事后排查)做防御。
六、企业侧清单:如果你管的是一个团队
前面第七节是给个人开发者的,这一节给需要管理多个项目的安全/运维同学。
① 制品治理:建私有源,统一入口
- 搭 npm/PyPI/Maven 的私有代理仓库,生产构建只允许从私有源拉包。
- 私有源开启延迟同步(新发布的包延迟 N 小时才进入私有源)——这一招很朴素但极其有效:绝大多数恶意包在被发现和下架的时间窗口内就失效了,延迟同步让这个窗口成为你的缓冲带。
② 依赖可见性:先搞清楚你有什么
- 生成全量 SBOM(软件物料清单),不只是直接依赖,要含传递依赖。
- 定期扫描:有没有包的发布时间异常(老包突然发新版)、维护者是否变更、是否新增了安装脚本。
③ 构建环境隔离
- 构建与发布分离成两个环境、两套凭据。构建被污染不应该有发布权限。
- CI 出站网络白名单化:只允许访问私有源和必要的服务。
- 优先用 OIDC 联邦身份换取短时效凭据,杜绝长期 token 常驻 CI。
④ 安装脚本管控
- CI 里默认
--ignore-scripts,只对确实需要编译的依赖开白名单。 - 这一条能挡住绝大多数「安装即执行」型的投毒——包括这次「沙虫」的主要执行路径。
⑤ 发布侧的最小权限
- 发布 token 只给发布权限,不给代码读写、不给管理权限。
- 强制 MFA,优先硬件密钥。
- 定期审计维护者列表:清理离职人员、闲置账号。
⑥ 应急预案:预演一次「我们被污染了」
- 明确三件事:谁有权决定停构建、凭据轮换清单在哪、对外通知谁。
- 事件发生时最贵的不是技术处置,是找人和找清单的时间。
七、为什么 CI/CD 是三起事件里最该关注的目标
把三起放在一起看,CI/CD 环境的重要性被严重低估了。理由有四条:
1. 它集中持有凭据。
CI/CD 里通常存放着 npm Token、云服务密钥、容器 registry 凭据、数据库密码(为了跑集成测试)。一次泄露,全部拿到。
2. 它的凭据通常是长期的。
本地开发者的 token 会因为换电脑、离职而轮换,但 CI 里的 secret 经常是「配好之后三年没动过」。
3. 它的网络出口通常不受限制。
为了能拉各种依赖、推各种镜像,CI runner 的出站往往是全通的。这对窃取到的凭据来说是完美的外传通道。
4. 它执行的代码来自外部。npm install 会执行依赖里的 postinstall 脚本——这是设计如此,不是漏洞。也就是说,CI 在主动执行从互联网下载的代码,而且用的还是高权限凭据。
把这四条合起来,会得到一个不太舒服的结论:CI/CD 环境是大多数组织里「权限最高、防护最弱、最长不被审计」的一台机器。
八、和 OWASP 智能体清单的对应
今天写的 OWASP 2026 智能体十大安全风险 里,ASI04 智能体供应链漏洞专门讲了这一点,其中点名的在野案例就包括:
- MCP 工具描述符投毒:GitHub MCP 中的提示词注入漏洞,恶意公共工具在元数据里隐藏命令
- 仿冒 Postmark 的恶意 MCP 服务器:npm 上首个被公开报道的在野恶意 MCP 服务器,秘密把邮件密送给攻击者
- 受污染的 npm 包被编程智能体自动安装,启用隐藏后门泄露 SSH 密钥与 API 令牌
ASI04 的核心判断和这三起事件完全一致:智能体生态与传统软件供应链的差别在于「运行时组合」——动态加载外部工具和 Agent 角色,使攻击面从「清单式的组件安全」转向「多样化且不透明组件的运行时安全」。
换句话说:AI 编程助手会帮你装包,而且不会替你做来源核验。 这让供应链攻击的传播链条又多了一环自动化的产能。
九、开发者的处置与预防清单
A. 怀疑已中招时的处置(顺序不能乱)
- 先断网隔离。可疑设备断开网络,防止继续外联与凭据继续被用。
- 再轮换凭据。GitHub Token、npm Token、云密钥、SSH 私钥、数据库密码全换;关联账号执行「退出全部设备」。
- 排查依赖文件。检查
package.json/ 各种 lock 文件 /node_modules,找异常的preinstall、postinstall。 - 清理残留。这一步最容易被跳过:检查 Claude Code hooks、VS Code 任务配置、
router_runtime.js、setup.mjs等位置。 - 最后才恢复构建。确认残留清理干净后再重新装依赖,否则会二次感染。
关于顺序:为什么要「先轮换再排查」?因为排查需要时间,而凭据泄露是持续进行的。先换掉凭据,等于先止血;之后再从容排查。
B. 日常预防(按性价比排序)
① 锁文件 + 锁版本
- 提交 lock 文件,且禁止 CI 自动更新 lock(
npm ci而不是npm install)。 - 依赖升级走单独的、可评审的 PR,不要混在业务改动里。
② 禁用不必要的安装脚本
- 对不需要编译的依赖,用
--ignore-scripts安装。 - 在 CI 里默认关闭自动执行脚本,只对白名单依赖放开。
③ 私有镜像 + 来源核验
- 搭建私有 registry(代理 + 缓存 + 扫描),生产构建只从私有源拉包。
- 这样即使上游被污染,也有一层缓冲与拦截窗口。
④ 发布凭据最小权限 + 短时效
- npm/GitHub 的发布 token 只给发布权限,不给读写代码、不给管理权限。
- 用短时效令牌,定期轮换。
- 开启 MFA,且优先用硬件密钥而非短信。
⑤ CI 凭据隔离
- 构建环境的 secret 不要长期有效,改为运行时动态获取(OIDC 联邦身份是首选)。
- 限制 CI 出站网络:能拉私有源就够了,不需要全通。
- 把「构建」和「发布」拆成两个环境、两套凭据——被污染的构建不应该有发布权限。
⑥ 关注发布记录,而不只是版本号
- 装包前看一眼:最近一次发布是什么时候、是谁发的、发布说明有没有异常。
- 紧急安全公告后,核验发布者与公告是否一致(TanStack 那起就是利用了这一点)。
三个反直觉的判断
一:「用的人越多越安全」在供应链领域不成立。
热门包意味着更大的攻击收益,也意味着更多下游受害者。选择依赖时,流行度是攻击价值指标,不是安全性指标。
二:「我们是大厂,用的都是知名包」不构成防护。
这次被污染的正是 echarts-for-react、@antv/g2、TanStack 系列——全是知名包。包的知名度决定它被攻击的概率,不决定它安全。
三:「我们 review 过源码」挡不住 CI/CD 劫持。
源码和产物之间隔着构建环节。如果你的验证方式是读源码,那么第三起事件正好绕过了它。真正的验证对象是产物(校验和、签名、来源证明),不是源码。
四:「锁了版本」不等于「换了 lock 文件」。
lock 文件只有在被遵守时才有效。如果 CI 里跑的是 npm install 而不是 npm ci,或者有人习惯性删掉 lock 重新生成,那么锁版本的防护就形同虚设。检查一下你的 CI 命令,这一步五秒钟就能做完。
十、一句话总结
三起事件,三个包管理器,三条入口——维护者账号、平台注册、CI/CD 流水线。
把它们放在一起,最值得记住的是两个变化:
- 从「投毒」到「传播」。蠕虫式自我复制让受害者变成传播者,于是「清除恶意版本」不再等于「终止事件」。这改变了应急响应的基本假设。
- 从「代码」到「流程」。当攻击目标是 CI/CD,源码干净就不再意味着产物安全。你审计的对象必须从「写了什么」扩展到「谁构建的、用什么凭据构建的、产物能不能被验证」。
还有一个共同的收尾细节值得记下来:三起事件的止损最终都依赖平台侧清理——下架恶意版本、暂停注册、轮换凭据,而不是依赖使用者自己发现。这意味着应急预案里「等上游处理」这条路径是要写超时时间的,不能无限期等。
附:三个值得记住的时间数字
这三起事件里有几个时间参数,对设定防御节奏很有参考价值:
「四天」 —— RubyGems 暂停新用户注册的天数。
这是平台从遭受攻击到恢复秩序所需的时间。对依赖 RubyGems 的团队来说,这四天意味着新包无法发布——如果你的发布流程依赖实时推包,需要有备选方案。
「600 余个」 —— 沙虫一次性投放的恶意版本数。
这个数量决定了人工排查是不现实的:你不可能逐个 review 依赖的每一个版本。所以防御必须落在自动化门禁(私有源 + 锁版本 + 扫描),而不是人工审核。
「48 小时」量级 —— 恶意包从发布到被社区发现下架的典型窗口。
这个数字不同来源统计不同,但量级是一致的:恶意包通常在数小时到数天内被发现。这也是为什么「私有源延迟同步」这一招有效——你只需要比发现时间晚一点同步,就能避开绝大多数污染。
第三个数字给出的策略很实用:不需要预测哪个包是恶意的,只需要比社区慢半拍。 在供应链安全上,「延迟」是一种防御资源。
素材来源
- 国家网络与信息安全信息通报中心 / 公安部网安局(2026-05-26,网信昆明转载):关于主流 JavaScript 软件包管理平台 npm 遭供应链投毒攻击的通报——沙虫手法链条、300 余包/600 余版本、受影响项目清单、五条处置建议,本文事件一全部细节均以此为准
- UVCyber 威胁通告(2026-05-20):TanStack Supply Chain Attack——TeamPCP、5 月 11 日、42 个 TanStack npm 包
- safedep(2026-05-12):Mass Supply Chain Attack Hits TanStack, Mistral AI npm and PyPI Packages——170 余 npm 包 + 2 PyPI 包、Mini Shai-Hulud 蠕虫
- 安全媒体对 RubyGems 事件的报道(2026-05):5 月 12—13 日洪泛、5 月 16 日恢复注册并清理 500 余个恶意包、暂停新用户注册四天
- FreeBuf(2026-09-14 前后):OpenAI Agents 关联 RubyGems 恶意攻击活动——归因报告内容;注:该归因目前存在争议,本文不作结论采信
评论