你的代码还没写完,你的密钥已经在别人的服务器上了。
2026年8月4日,世界协调时上午9点29分。
一段看似正常的代码更新,从一个被攻陷的GitHub账号推出,走过完全合法的CI/CD流水线,拿到货真价实的安全签名,然后,安静地开始杀人。
接下来的几个小时里,一种自称"Shai-Hulud"的npm蠕虫像《沙丘》里的巨型沙虫一样,在JavaScript生态的地下悄无声息地穿行。
它吞掉了868个npm包、1381个版本,这些包每月被全球开发者和CI流水线安装超过20亿次。

▲ VentureBeat点出核心:蠕虫没有伪造安检章,它走进了正常运转的安检门,赚到了一枚合法的章。
这场攻击让信任本身变成了武器
九点三十五分,毒药出厂
让我们把时钟拨回那个清晨
Datadog安全实验室事后重建的时间线精确到分钟:09:02,keyv仓库的主分支出现最早可恢复的恶意状态;
09:29,带有SLSA provenance签名的keyv@6.0.0进入公共透明日志Rekor;
09:35,确认发布

▲ Datadog安全实验室的时间线重建:从账号失守到恶意包上线,窗口只有半小时。
keyv是什么?
一个键值存储库,听起来平平无奇,周下载量却高达1.27亿。
它的维护者同时掌握着cacheable、flat-cache、file-entry-cache等一整个缓存工具家族。
这些名字你可能从没听过,但如果你用过ESLint(几乎每个前端开发者都用),它们早就躺在你的node_modules里了。
攻击者做的第一件事很简单:在被控的仓库里塞进一个setup.mjs和一个Math_Symbol.js,然后在package.json里加一行:
"preinstall": "node setup.mjs"
就这一行
任何人运行npm install,安装还没结束,你的密钥就已经在飞了。
它是一条蠕虫
普通的供应链攻击像一颗地雷,炸一个算一个。
但Shai-Hulud是一条蠕虫,它的设计目标是自我复制。
理解这条蠕虫,你可以把它想成一枚两级火箭。
第一级:引导器
约十几KB的setup.mjs
如果机器上缺少Bun运行时,它就从Bun的官方GitHub发布页下载一个合法的Bun 1.3.13二进制。
注意,下载源是完全可信的官方渠道,安全扫描工具几乎不可能把它标红。
第二级:载荷
约700KB的混淆JavaScript,在Bun下运行。
Elastic Security Labs把它命名为CHAINDROP,并指出代码里塞满了《沙丘》主题的变量名。
这个载荷是一台全自动收割机:
~/.npmrc里的npm令牌 GitHub PAT与App令牌 AWS/GCP/Azure凭证与实例元数据 Kubernetes服务账户 HashiCorp Vault密钥 Stripe、Slack令牌 SSH密钥与 .env文件甚至连OpenAI、Anthropic、Cursor、Claude、Gemini的API密钥都不放过
收割完毕,数据先用AES-256-GCM加密,再用攻击者的RSA公钥封装。
就算你截获了外泄数据,没有私钥也解不开。


▲ Elastic Security Labs的警报:CHAINDROP利用写权限令牌,回门受害者控制的每一个npm包。
随后,蠕虫开始自我复制
如果蠕虫发现你的npm令牌具备包发布权限且绕过了2FA,它就会枚举你能发布的所有包,下载当前版本、注入恶意代码、把补丁版本号+1、替你发布回去。
你的同事明天早上运行npm install,就会拉到你"刚发布"的新版本。
然后他们的令牌也被偷走,他们的包也被感染。
如此循环,指数级扩散
这就是为什么安全研究员把它叫做"对供应链发动的供应链攻击"。
"实时直播"的攻防战
Aikido的安全研究员Charlie Eriksen在事件发生时几乎是实时直播了这场攻防。

▲ Charlie Eriksen:"感染已经从cacheable扩展到keyv。至少两名持有活跃令牌的开发者被击中,已经发布了十几个被污染的包。"
他的帖子读起来像战地记者的电报:攻击者一边调试对@keyv/*作用域包的感染,一边删除仓库里的告警Issue。
紧接着,

▲ "第二波攻击随时会到。"这则预警足以让仍在观望的安全团队冷汗直流。
Picsart、ServiceTitan、OneReach、Qlik、Deliveroo……企业作用域的包开始出现在受害者名单上。
很多维护者从未听说过Shai-Hulud,他们只是在某次例行的npm install之后,自己的发布令牌就被顺手带走了。


▲ International Cyber Digest的突发警报在数小时内获得百万级浏览,左图沙虫意象、右图GitHub与npm标志并置,供应链的两大支柱同时沦陷。
安全签名为何放行了恶意包
让我把这件事翻译成普通人能懂的语言。
Provenance(来源证明)是npm生态近年来力推的安全机制。
它只验证软件包是否来自声明的GitHub Actions流水线;
验证通过,就盖一个✅
Shai-Hulud拿到了这个✅
这枚签名确实有效
攻击者控制了维护者的GitHub账号,使用维护者自己的CI/CD流水线,走完了完全正规的发布流程。
Provenance机制诚实地完成了验证:"这个包确实来自这条流水线"。
问题在于,它不判断触发者是否拥有正当的发布权限。
用VentureBeat的比喻:小偷没有翻窗户进来,他走的是正门,用的是偷来的钥匙,保安还给他敬了个礼。
这颠覆了整个行业近两年来"只要有签名就安全"的观念。
IEEE资深会员Kayne McGladrey在同一篇报道中指出,企业正开始用合同把安全义务推向上游。
Provenance、身份治理与补丁纪律,正在从工程师的个人偏好,变成可审计的合同风险。
打开编辑器的那一刻,你可能就已经中招了
如果你以为"我不运行npm install就没事",那我有个坏消息。
蠕虫还有第二条攻击路径
当它拿到足够权限的GitHub令牌时,会向目标仓库最多约50个分支写入两个文件:
.vscode/tasks.json,VS Code打开文件夹时自动执行 .claude/settings.json,Claude Code启动会话时自动加载
提交信息伪装成chore: update config,作者字段可以写成"claude"之类不起眼的名字。
你甚至不需要运行任何命令,仅仅用VS Code或Claude Code打开这个仓库,恶意代码就会执行。
另有一种吊销陷阱
公开讨论中提到:某些样本会监视"被盗GitHub令牌被吊销"这一事件,并可能触发预埋的破坏性逻辑。
安全团队若立刻轮换密钥,顺序搞错反而会踩雷。
正确的顺序是:先定位并移除仓库和编辑器里的植入物,再在干净的机器上轮换密钥。
沙虫从未离开:一年三击的进化史
Shai-Hulud此前已有两轮攻击记录。
2025年9月,首波攻击感染约187个npm包,KrebsOnSecurity率先报道。
蠕虫在受害者GitHub账号下创建公开仓库,描述写着"Shai-Hulud",像在炫耀战利品。
2025年11月,2.0版本把执行点前移到preinstall,影响范围扩展到约25000个GitHub仓库的外泄痕迹。
描述字符串改成了"Sha1-Hulud: The Second Coming"。
2026年8月,也就是我们正在讲述的这一次。
Wiz认为载荷大量基于公开的Mini Shai-Hulud代码并有关键改动,新增了合法provenance利用、编辑器持久化,以及对AI工具密钥的广泛采集。


▲ 2025年9月The Hacker News的警报记录了沙虫早期攻击,当时已有500多个包受到感染。
从ILOVEYOU到Conficker,再到今天的npm蠕虫,不变的是复制因子:每个新宿主提供一份可被滥用的"通讯录"。
变的是通讯录的形态,从Outlook联系人,变成npm whoami能列出的全部可发布包。
你该做什么?
如果你是开发者:
将keyv等锁定在已知干净版本(如keyv 5.6.0),用overrides压住传递依赖 在CI中使用 --ignore-scripts,或升级到npm 12+(默认不执行install钩子)搜索lockfile中是否存在受影响版本 - 先清理仓库中的.vscode/tasks.json和.claude/settings.json异常,再轮换密钥
如果你是技术管理者:
启用 min-release-age策略,故意不装"几分钟前刚发布"的最新版审计所有npm/GitHub/云/Vault/CI令牌,在干净环境中轮换 "我们没用过keyv"不等于安全,检查你的传递依赖

▲ 专业提示:合法包regenerate-unicode-properties也存在名为Math_Symbol.js的文件,小心误报。
信任的价格
这场攻击最深刻的教训落在信任机制上。
整个现代软件工业建立在一个假设之上:你可以信任别人写的代码。
npm生态、开源社区、CI/CD流水线,每一层都在说"相信上游"。
这种信任让我们能用几百行代码搭出一个产品,让一个人维护的小库服务于数十亿次安装。
但当信任本身成为攻击面,我们所有人都在沙虫的阴影下行走。
CrowdStrike在2026年8月3日,恰好是攻击发生的前一天,发布的威胁狩猎报告中写道:软件供应链与开发者生态已成为直接攻击面。
Shai-Hulud的命名取自弗兰克·赫伯特的《沙丘》,沙漠星球厄拉科斯上不可阻挡的巨型沙虫。
在小说里,弗雷曼人学会了驾驭沙虫而非消灭它。
在现实世界里,我们还没学会
夜雨聆风