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 的 SessionStart hook
  • Codex 相关配置
  • VS Code 的 `runOn.folderOpen` task
  • 本机上的 kitty-monitor systemd 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.servicegh-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 变成一串凭证事故。