22分钟,637个恶意版本。
SafeDep 披露,2026年5月19日 01:44 至 02:06 UTC,npm 账号 atool 被攻陷。攻击者随后向317个包发布恶意版本。原文标题和正文里有314与317两个数字不一致,按正文统计口径,这里采用317。
受影响包里有 size-sensor、echarts-for-react、@antv/scale、timeago.js,也包括大量 @antv scoped packages。这些名字对前端项目并不陌生,尤其是可视化、React 组件和工具链项目。
但边界也要说清楚:不是所有装过这些包的项目都已被入侵。真正有风险的是解析到了恶意版本,并且执行了安装脚本的环境。
这也是这件事反常的地方。它看起来像 npm 投毒,实际更像一次供应链蠕虫式攻击。它的目标不是只污染一个依赖,而是沿着安装、CI、GitHub、AI 编程工具和开发者机器继续往下钻。
入口很普通,放大器是 semver 和 install script
这次入口并不复杂:atool 账号失陷,攻击者自动化发布新版本。
真正放大攻击面的是 npm 生态里很常见的两件事:semver 自动解析,以及安装阶段脚本执行。
恶意版本在 package.json 里加入了:preinstall: bun run index.js。这意味着依赖安装时,脚本就可能先跑起来。
多数版本还通过 optionalDependencies 指向 github:antvis/G2#commit,投递第二份 payload。这里应理解为依赖链被滥用,不能把 AntV/G2 或相关维护者直接定性为攻击方。
| 项目 | 已披露事实 | 对项目的影响 |
|---|---|---|
| 时间窗口 | 2026-05-19 01:44-02:06 UTC | 自动化发布,人工响应时间很短 |
| 规模 | 317个包、637个恶意版本 | 高下载依赖被卷入,误装概率上升 |
| 关键包 | size-sensor、echarts-for-react、@antv/scale、timeago.js、大量 @antv scoped packages | 前端、可视化、React 项目优先排查 |
| 执行路径 | preinstall: bun run index.js,并通过 optionalDependencies 再投递 | 只看业务代码 diff 不够 |
| 风险边界 | 解析到恶意版本并执行安装脚本才有直接风险 | 不能把“使用相关包”等同于“已经被入侵” |
一个容易踩坑的判断是:latest dist-tag 没变,不代表安全。
因为项目通常不会把依赖都钉死到单个版本。只要 semver 范围允许,清掉 lockfile 后重装,或在新环境里重新安装,就可能解析到更高的恶意版本。
对前端维护者来说,最现实的动作不是立刻大范围重装。更稳妥的是先保留现有 lockfile,确认解析版本,再决定回滚、锁定或清理构建环境。
危险点不只是窃密,而是借流水线“合法化”
SafeDep 称,这次 payload 是约498KB的混淆 Bun 脚本,并认为它与三周前 SAP 相关事件中的 Mini Shai-Hulud 工具链高度相似。
脚本会扫描多类凭证:npm、GitHub、AWS、GCP、Azure、Kubernetes、Vault、SSH、Docker 等。它还会尝试用被盗 GitHub token 创建公开仓库,把数据提交出去。
这已经不是“偷一个 npm token”那么简单。
更麻烦的是 GitHub Actions 场景。恶意脚本会尝试用 OIDC 换取 npm publish token,并使用 Sigstore 签名。
这一步很关键。攻击者如果借到了流水线自己的身份,后续发布和签名会更像正常流程。对安全团队来说,日志里看到“有签名”“来自 CI”,不能直接等同于可信。
它还尝试向开发工具和仓库配置里塞持久化入口,包括:
- CodeQL workflow,例如
.github/workflows/codeql.yml - Claude Code 的
SessionStarthook - Codex 相关配置
- VS Code 的 `runOn.folderOpen` task
- 本机上的
kitty-monitorsystemd service 或 macOS LaunchAgent
这类设计说明,攻击者盯的是开发链路里的缝隙:仓库权限、CI 权限、AI 编程工具、本机环境,哪一处能留下来,就从哪一处继续。
拿2018年的 event-stream 事件做对照,当年的核心问题更集中在依赖包被污染。这次的麻烦在于,依赖只是入口,后面接的是构建系统和开发者工具。
对 DevSecOps 团队来说,处置成本也因此上升。不能只让业务团队改 package.json。CI runner、GitHub token、npm publish 权限、云凭证,都要纳入同一张排查清单。
现在该查什么:版本、IoC、锁文件、构建环境
最相关的两类人,是前端/Node.js 项目维护者,以及负责供应链安全的 DevSecOps 团队。
前端维护者先做版本确认。看 lockfile 里是否出现5月19日该时间段由 atool 发布的恶意版本。npm、pnpm、yarn 都一样,重点不是包管理器名字,而是实际解析到了哪个版本。
不要急着删除 lockfile 后重装。这个动作可能把原本没命中的环境,重新解析到问题版本。先备份,再查版本,再处理。
DevSecOps 团队要把安装过相关版本的机器和 CI runner,当作可能暴露凭证的环境处理。这里的判断要偏保守,因为 payload 的目标就是凭证和持久化。
排查时至少看这些点:
- `package.json
是否出现preinstall: bun run index.js` - lockfile 是否锁到相关恶意版本
- 是否出现 `@antv/setup.github:antvis/G2#1916faa365f2788b6e193514872d51a242876569` 等 optional dependency
- GitHub 仓库是否出现
chore/add-codeql-static-analysis分支 - 是否出现名为
Run Copilot的 CodeQL workflow - 是否出现 `.claude/setup.mjs
、.vscode/tasks.json` - 本机或 runner 是否出现
kitty-monitor.service、gh-token-monitor - GitHub 账号下是否被创建异常公开仓库,用于外传数据
处理顺序上,我更在意三件事。
第一,锁住版本。对命中过恶意版本的项目,回滚到确认安全的版本,并保留证据,避免边查边覆盖现场。
第二,轮换凭证。npm、GitHub、云厂商、Kubernetes、Vault、SSH 等凭证都在扫描范围内。只清依赖,不换凭证,风险会留下尾巴。
第三,收紧构建权限。能禁用不必要 install scripts 的地方就禁用;CI 的 OIDC 换发范围要缩小;npm publish 权限不要挂在过宽的流水线上。
现实约束也在这里。很多团队没法一刀切禁用所有 install scripts,因为前端生态里确实有包依赖安装阶段构建。可行的折中是:生产构建环境更严格,新增依赖有冷却期,高权限发布流水线单独隔离。
接下来最该观察的,不是攻击者是谁。现在证据还不足以支撑身份判断。
更实际的变量是三个:npm 是否加强短时间批量发版和 install script 的风控;GitHub Actions 的 OIDC 与 npm publish token 换发是否收紧;企业内部是否把“依赖升级”和“凭证暴露”放进同一个响应流程。
这次事件给开发团队的提醒很直接:依赖安装不再只是拉代码。它可能是在给陌生脚本一次进入 CI、仓库和本机工具的机会。慎始如终,才不至于让一行 semver 变成一串凭证事故。
