更新日期:2026-10-05
2026 年的 npm 生态被三起风格完全不同的供应链攻击轮番敲打:3 月 31 日,axios 的两个在用版本被注入恶意依赖 plain-crypto-js,载荷含远程访问木马(CISA 于 4 月 20 日发布通报);5 月,一个注册仅数小时的新维护者一口气发布 14 个 typosquat 包,用 preinstall 钩子偷 AWS、Vault 与 npm publish 凭据(Microsoft 安全博客 5 月 28 日复盘);8 月初,热门维护者账号被接管,keyv、cacheable 等约十个直接依赖被注入蠕虫化载荷,窃取的 npm token 又被用来继续投毒(Arctic Wolf 8 月 6 日披露)。
三起案例放在一起看,手法在进化——从依赖注入到凭证窃取再到自传播蠕虫——但被反复利用的链路只有三条:安装脚本自动执行、发布凭据被盗、对维护者身份的过度信任。平台侧的响应也在这两年密集落地:OIDC trusted publishing、granular token、classic token 停发、安装脚本管控命令。本文按「案例复盘 → 共性拆解 → 平台时间线 → CI 防御落地 → 应急剧本」组织,目标是让团队把防御清单直接落进 CI,而不是停留在「又出事了」的感叹。既有基础:站内 npm 12 安全迁移指南:安装脚本白名单、Git 依赖与 CI 兼容 讲过 npm 12 的脚本管控迁移,本文不重复其配置细节;发布侧的恶意扫描与 OIDC 实践见 npm 发布前恶意软件扫描实战:双用途包、OIDC 与分阶段发布。
适用范围:使用 npm 生态(npm/pnpm/yarn)的开发与运维团队,无论是否自发布包——三起案例里两起的受害者是纯消费方。不适用场景:完全离线的内网镜像源环境(风险面不同,但镜像源的入库扫描责任更重);期待「装个工具就安全」的团队——本文的清单里,凭据轮换与应急剧本占一半篇幅,那部分没有工具替代。
先看结论
- 三起案例三个入口:依赖声明被注入(axios)、包名仿冒(typosquat)、维护者账号接管(keyv/cacheable)。防御必须同时覆盖「装什么」与「装的时候执行了什么」。
- 安装脚本是共同的执行点:三起载荷全部经 preinstall/依赖链自动执行。ignore-scripts=true 加显式白名单,是当前性价比最高的单点防御。
- 蠕虫已经在用被盗凭据自传播:keyv/cacheable 事件中,窃取的 npm publish token 被直接用于污染更多包——发布凭据的泄露半径第一次在真实事件里指数化。
- 载荷盯的是云与 CI 凭据,不是你的代码:AWS IMDSv2/ECS 角色凭据、Secrets Manager(16+ 区域)、Vault token、GitHub PAT、npm publish token——开发机与 CI runner 就是新的「数据库」。
- 持久化手段已经盯上 AI 工具配置:keyv/cacheable 事件中载荷经 .vscode、.claude 配置持久化。装了 AI 编码助手的开发机,配置目录也要进安全盘点。
- 平台侧的基本盘已经齐了:trusted publishing(OIDC,免长期 token)、写权限 granular token 默认 7 天、classic token 停发、npm approve-scripts 系列命令。发布方与消费方都有现成动作,剩下的是执行。
- min-release-age 类的新包冷却策略已被官方通报采纳:CISA 在 axios 事件的缓解建议里明确写入 .npmrc 设 min-release-age=7——「不装刚发布的版本」从最佳实践变成了官方口径。
- 应急响应的速度决定损失半径:凭据轮换的顺序(VCS → CI/CD → 云 → npm → SSH)、排查窗口的界定、重建的干净基线,要写成剧本,不要在事发当天现场发明。
三起案例的时间线与手法
| 案例 | 时间 | 入口 | 载荷与目标 | 权威来源 |
|---|---|---|---|---|
| axios 依赖注入 | 2026-03-31(通报 04-20) | axios@1.14.1 与 axios@0.30.4 的依赖声明被注入 plain-crypto-js@4.2.1 | 多阶段载荷下载,含 RAT;CISA 建议降级 1.14.0/0.30.3 并轮换全部凭据 | CISA 通报;axios 官方 post-mortem(GitHub issue #10636);Microsoft 归因分析(2026-04-01,归因 Sapphire Sleet,以原文为准) |
| typosquat 窃密 | 2026-05(通报 05-28) | 新维护者 vpmdhaj 4 小时内发布 14 个仿冒包(仿 OpenSearch 等生态,版本号虚高如 1.0.7265) | preinstall 执行;两代 stager(Gen-1 HTTP C2,Gen-2 滥用合法 Bun 运行时作加载器);偷 AWS IMDSv2/ECS 角色凭据、Secrets Manager(16+ 区域)、Vault token、npm publish token | Microsoft Defender 安全团队博客;npm 收到反馈后下架 |
| keyv/cacheable 账号接管 | 2026-08-04 或更早(披露 08-06) | 热门维护者 npm 账号被接管;keyv@6.0.0、cacheable@2.5.1、cacheable-request@13.0.20、flat-cache@6.1.24 等约 10 包被污染 | preinstall → 下载 Bun → 混淆二阶段 → 偷 npm/CI/云/GitHub PAT/Vault/SSH 凭据上传攻击者仓库 → 经 .vscode、.claude 配置持久化 → 用被盗 token 继续投毒(自传播) | Arctic Wolf 披露;第三方评估波及面或达数百包(口径不一);npm 上恶意版本已移除,keyv latest 回到 5.6.0 |
三个案例的受害路径值得各记一句:axios 是「你信任的包,它的依赖声明变了」——供应链信任的根是包的发布身份,依赖树里任何一层的变更都该被 lockfile 钉住并审查;typosquat 是「你以为你在装 A,其实装的是 A-1」——对安装来源的身份核验(trusted publishing、provenance)是平台给出的解法;keyv/cacheable 是「你信任的维护者账号,已经不是他本人在操作」——账号安全(MFA、token 时效)从「发布方的家务事」变成了全生态的安全基础设施。
共性拆解:被反复利用的三条链路
链路一:安装脚本自动执行。 preinstall/postinstall 是 npm 包安装期的任意代码执行点,三起案例的载荷全部由此起步。这是整个 2025–2026 平台整改的核心,也是消费方防御的第一优先:npm CLI v12 已提供 npm approve-scripts、npm deny-scripts、npm install-scripts 系列命令做脚本管控(迁移细节见站内 npm 12 文)。消费方的底线配置:
# .npmrc(项目级,随仓库分发)
ignore-scripts=true
audit=true
fund=false
需要脚本的包(常见:esbuild、sharp 等原生二进制类)显式白名单放行,白名单进版本控制、变更走 review——这个模式的完整迁移步骤与 CI 兼容问题,站内 npm 12 迁移指南已经写过,不重复。
链路二:发布凭据(以及一切凭据)的被窃与滥用。 注意载荷偷的不只是 npm token:GitHub PAT、云角色、Vault、SSH 私钥全在购物清单里,而且 keyv/cacheable 事件证明偷到的 npm publish token 会被立即用于继续投毒。推论有两条:第一,你的 CI 凭据作用域就是攻击者的行动半径,发布 token 用 granular + 短时效(平台已把写权限 token 默认压到 7 天)、云凭据走 OIDC 短会话、机器上不落地长期密钥;第二,「凭据轮换」是应急剧本的第一章而不是脚注,后文第五节给完整顺序。
链路三:对维护者身份的信任。 typosquat 案例里,伪造的仓库元数据与虚高的版本号利用的是「看名字像官方」;账号接管案例里,被接管的正是社区信任本身。消费方能做的对应动作:关键依赖固定到经过验证的版本区间,升级窗口里核对 provenance(npm 的 provenance 基于 Sigstore,含 provenance 与 publish 两类 attestation),对「突然出现的新维护者发布的热门名字」保持本能怀疑——typosquat 案例里那个 4 小时发 14 个包的账号,在任何一个有人看一眼发布历史的流程里都活不过一分钟。
平台侧响应:npm/GitHub 的整改时间线
平台在 2025–2026 年的动作密集且方向明确,发布方按这个时间线对齐自己的流程:
| 时间 | 变更 | 对你的意义 |
|---|---|---|
| 2025-07-31 | OIDC trusted publishing GA | 发布不再需要长期 publish token,CI 里无密钥可偷 |
| 2025-09-29 | 认证收紧:新 TOTP 停用;写权限 granular token 默认 7 天、上限 90 天 | 「一把 token 用一年」的时代结束 |
| 2025-11-05 | classic token 停发 | 存量 classic token 要迁移,拖到最后是断发布 |
| 2026-02-18 | npm trust 批量配置 + --allow-git(CLI v11.10.0+,none 拟成 v12 默认) | Git 依赖的门禁化 |
| 2026-04-06 | trusted publishing 支持 CircleCI | CI 覆盖面扩大 |
| 2026-07-31 | bypass-2FA token 限制 | 双因素绕过通道收紧 |
| 2026-09-18 | stage-only token | 发布前的 staged 验证有了专用凭据形态 |
消费方视角的解读:平台把「长期高权限凭据」这个最大的单点逐步拆掉了,发布方的迁移义务也随之到期——还在用 classic token 或超长 granular token 的发布流程,既是合规欠账也是下一次事件的现成入口。发布侧的完整改造(OIDC、扫描、分阶段发布)见站内 npm 发布扫描一文。
CI 与开发机防御落地
把前面的分析压成可以今天执行的动作,按优先级:
# 1. 消费方:确认项目没有踩过 axios 事件(替换为你的锁文件路径)
grep -n "plain-crypto-js" package-lock.json pnpm-lock.yaml 2>/dev/null \
&& echo "命中:立即按 CISA 通报处置" || echo "未命中"
npm ls axios 2>/dev/null | head -5 # 核对 axios 版本是否在受影响版本段
# 2. 消费方:CI 统一用 npm ci + 忽略脚本(白名单另行管理),不再用 npm install
# CI 中的安装步骤示例:
# npm ci --ignore-scripts && npm rebuild <白名单包>
# 3. 消费方:新包冷却 —— .npmrc 加 min-release-age(CISA 在 axios 缓解建议中采用)
# min-release-age=7 # 只安装发布超过 7 天的版本,给安全社区留发现窗口
# 4. 发布方:核对发布链路已迁移 trusted publishing,仓库里不应存在长期 publish token
grep -rn "_authToken\|NPM_TOKEN" .github/workflows/ .gitlab-ci.yml 2>/dev/null | head
第 3 条多说两句:min-release-age 的价值在把「投毒到被发现」的时间窗从「你的构建机」转移到「安全社区」——axios 从注入(3-31)到 CISA 通报(4-20)隔了 20 天,keyv/cacheable 从接管到披露约两天,这个窗口内的「最新版」都是高风险品。7 天是官方建议的起点,关键业务可以更长。代价是热修复会晚几天拿到,这是值得明码标价的权衡,写进团队规范即可。
开发机同样在爆炸半径里(typosquat 的主要目标就是开发机):AI 编码助手的配置目录(.claude、.vscode)已被用于持久化,把它们纳入端点安全的盘点范围——配置目录的异常修改、未知 MCP/plugin 条目、以及安装 AI 工具的渠道管控。相关工作流的加固思路见站内 Claude Code 最佳实践:上下文、权限、工作流与团队协作的实战经验。
应急响应剧本:假设明天早上发现中招
按时间顺序的处置清单,现在写好,事发当天照做:
- T+0 确认范围:命中哪个包哪些版本;npm ci/npm install 在哪些机器与流水线执行过(axios 事件的排查口径就是「执行过 install/update 的仓库、CI 与开发机」)。
- T+30min 切断外联:按通报封锁攻击者基础设施(CISA 通报给出的 IOC,如 axios 事件的 Sfrclak[.]com);CI 出口防火墙临时收紧到白名单。
- T+1h 凭据轮换,顺序按「被盗凭据的行动半径从大到小」:云角色与长期密钥 → CI/CD 凭据(含 GitHub PAT、OAuth App)→ npm publish token → VCS 部署密钥 → SSH 主机与个人密钥。轮换 ≠ 改密码,旧凭据的吊销确认是完成标准。
- T+2h 排查滥用:云审计日志(CloudTrail 等)查异常 API 调用与新生成的凭据;GitHub 审计日志查新增的 deploy key、workflow 修改;npm 侧查账号名下的非本人发布。
- T+4h 环境重建:开发机与 CI runner 不做「清洗」,直接重装或换新实例——载荷含 RAT 与持久化时,清洗的可靠性永远不够。
- T+1d 复盘入册:感染路径、检测缺口、清单缺口,回写进防御清单与本文第七节的检查表。
剧本的价值全在「现在写好」:轮换顺序、排查命令、重建基线,事发当天靠记忆与临场发挥的团队,通常在第三步就乱序——先改了 npm 密码,云凭据还在攻击者手里跑。
三起事件的检测信号对照
复盘的另一个用途是把「下次怎么更早发现」写清楚。三起案例如果当时在你的环境里,最早的检测信号分别是:
| 案例 | 最早的本地检测点 | 信号形态 |
|---|---|---|
| axios 依赖注入 | 锁文件 diff 审查 | package-lock.json 出现从未来过的新增依赖(plain-crypto-js) |
| typosquat 窃密 | 安装日志 + 出站流量 | 全新维护者的包被安装;安装期出现到非常用域名的 HTTP 外联 |
| keyv/cacheable 蠕虫 | CI 出口日志 + GitHub 审计 | CI runner 到异常 GitHub 仓库的写流量;组织下出现未知 PAT 调用 |
把这三条变成常态化机制:锁文件 diff 进 review(依赖变更的可见性,是性价比最高的一单投入);CI 出口流量基线(npm registry、你的 git 托管、镜像源之外的外联默认告警);组织级 GitHub/npm 审计的周度复核(新增 deploy key、新 PAT、非本人发布)。三起案例的攻击者动作在信号层面都很吵——它们没有能力隐藏,是环境里「没人看」让它们安静地走完了流程。
把防御写进入职与外包流程
防御清单通常止步于 CI 配置,但人的环节同样有洞:新成员入职的装机清单里,固定包含「启用 npm 脚本管控、配置内部镜像源、安装密钥扫描工具」三步——这三步在个人机器上缺位的开发者,就是 typosquat 案例里最标准的入口;外包与临时协作的仓库访问,走「最小权限 + 时间盒」发放,并且他们推送的依赖变更过与全职员工相同的 review 门禁——供应链攻击者最喜欢的身份就是「权限齐备但没人认识的协作者」;离职与项目结束的回收清单里,明确「npm/GitHub 组织成员身份、发布权限、CI secret 的访问」的移除确认。
这三个触点的共同点是:它们都是流程变更而非技术变更,落地阻力全部在「没人觉得是自己的事」——把每个触点写进对应角色的 checklist(招聘负责人、项目负责人、离职经办人),责任到人后,执行的持久性才谈得上。
日常与事件期的验证命令,对应三起案例的检测点:
# 1. 依赖签名与完整性:npm audit signatures 核对 registry 签名(对应 axios 类注入)
npm audit signatures
# 2. 锁文件中的可疑新增依赖审查(对应 axios:检查树里突然出现的包)
grep -nE '"(plain-crypto-js|node-fetch-js|crypto-validator)"' package-lock.json || echo " (clean)"
npm ls --all 2>/dev/null | wc -l # 依赖树规模基线,突增要能解释
# 3. 新版本冷却:检查待装版本与发布时间的间隔(对应 keyv:发布即中毒)
npm view <pkg> time --json | jq -r 'to_entries[-3:][] | "\(.key) \(.value)"'
# 4. 发布方核对:安装前看一眼维护者与发布历史(对应 typosquat:4 小时 14 个包)
npm view <pkg> maintainers
这四条命令的产出物进 PR 描述(锁文件变更 review 时),「谁装的新包、为什么、维护者是谁」三个问题在合并前回答——typosquat 案例里那个新维护者账号,在这一步就会被拦下。
- 依赖安装统一走 npm ci(或等价冻结安装),npm install 不出现在 CI 配置里。
- ignore-scripts=true 为项目默认,脚本白名单在版本控制中且变更走 review。
- 新包冷却策略(min-release-age)生效,关键项目冷却期 ≥ 7 天并有豁免流程。
- 发布凭据已迁移 trusted publishing 或 granular 短时效 token,classic token 清零。
- CI 出口流量有基线,registry/托管/镜像源之外的外联有告警。
- 组织级 GitHub/npm 审计日志的周度复核有人负责、有记录。
- 应急剧本(第五节)完成过一次桌面推演,轮换顺序与联系人名单是新的。
# 周度依赖体检(进 CI 定时任务或本地 alias)
npm audit --audit-level=high # 高危及以上漏洞清零
npm outdated # 落后版本清单,重点看直接依赖
npm ls --depth=0 > /tmp/deps-$(date +%F).txt # 依赖规模快照,突增可解释
diff /tmp/deps-last-week.txt /tmp/deps-$(date +%F).txt || echo "依赖有变化,走 review"
官方资料与继续阅读
外部官方与权威链接:
- CISA 通报:Supply Chain Compromise Impacts Axios Node Package Manager(2026-04-20):https://www.cisa.gov/news-events/alerts/2026/04/20/supply-chain-compromise-impacts-axios-node-package-manager
- Microsoft 安全博客:Typosquatted npm packages used to steal cloud and CI/CD secrets(2026-05-28):https://www.microsoft.com/en-us/security/blog/2026/05/28/typosquatted-npm-packages-used-steal-cloud-ci-cd-secrets/
- Microsoft:Axios 事件缓解指引(2026-04-01):https://www.microsoft.com/en-us/security/blog/2026/04/01/mitigating-the-axios-npm-supply-chain-compromise/
- Arctic Wolf:keyv/cacheable 活跃供应链攻击披露(2026-08-06):https://arcticwolf.com/resources/blog/active-supply-chain-attack-on-npm-packages-keyv-cacheable-immediate-mitigation-required/
- axios 官方 post-mortem(GitHub issue #10636):https://github.com/axios/axios/issues/10636
- npm provenance 文档:https://docs.npmjs.com/generating-provenance-statements
- npm trusted publishing 文档:https://docs.npmjs.com/trusted-publishers
- GitHub Changelog:npm 安全更新时间线(classic token 停发等):https://github.blog/changelog/2025-11-05-npm-security-update-classic-token-creation-disabled-and-granular-token-changes
站内相关文章:
- npm 12 安全迁移指南:安装脚本白名单、Git 依赖与 CI 兼容——脚本管控的完整迁移步骤
- npm 发布前恶意软件扫描实战:双用途包、OIDC 与分阶段发布——发布侧的配套防御
- Next.js 16.3.3 安全升级指南:两项 Critical RCE、版本检查与回滚——框架侧安全响应的平行案例
- Claude Code 最佳实践:上下文、权限、工作流与团队协作的实战经验——AI 工具配置目录的安全盘点背景
事实与日期边界:三起案例的时间、手法与缓解建议均来自上列 CISA/Microsoft/Arctic Wolf/axios 官方原文,截至 2026-10-05;波及包总数的第三方统计口径不一,本文采用保守表述;平台时间线来自 GitHub 官方 changelog 与 npm 文档;攻击者基础设施指标(IOC)以通报原文为准,本文仅引用已公开且做了防御性脱敏处理的条目。

