7 月 11 日,JavaScript 代码保护公司 Jscrambler 的官方 npm CLI 包被植入恶意代码。五个版本在约三小时内接连发布——8.14.0、8.16.0、8.17.0、8.18.0 和 8.20.0——每个版本都携带一个用 Rust 编写的跨平台窃密程序。Socket.dev 在首个恶意版本发布六分钟后即检测到异常。
这不是一起普通的 npm 供应链攻击。恶意程序的狩猎清单里出现了以往同类攻击从未覆盖的目标:Claude Desktop、Cursor、Windsurf、VS Code、Zed 等 AI 编码工具的配置文件。攻击者在寻找这些工具中存储的 API 密钥和 Model Context Protocol(MCP)服务器凭据。
攻击链:从 npm install 到内核级访问
攻击的入口是一个看似无害的 preinstall 脚本。jscrambler@8.14.0 的 package.json 比上一个干净版本多了一行 "preinstall": "node dist/setup.js"。setup.js 是一个仅 43 行的 JavaScript 加载器——代码干净、无混淆,甚至用了一个带有欺骗性的函数名 ensureNativeRuntime 来伪装成合法的运行时初始化。
加载器读取 dist/intro.js——一个 7.8MB 的文件,.js 扩展名是伪装。它的真实身份是一个以 \x1bCSI\x01 为魔数的自定义二进制容器,内部打包了三个平台对应的 gzip 压缩原生可执行文件:Linux x86-64 ELF、Windows PE32+ 和 macOS arm64 Mach-O。
安装时,setup.js 根据 process.platform 选择匹配的二进制,解压到系统临时目录下随机命名的隐藏文件中,然后以 detached: true, stdio: 'ignore' 方式启动——这意味着恶意进程完全脱离 npm 进程树,在后台持续运行。
但在 8.18.0 和 8.20.0 版本中,攻击者改变了策略。preinstall 钩子和 setup.js 被移除。取而代之的是,完全相同的投放逻辑被作为一个自执行函数注入到 dist/index.js 和 dist/bin/jscrambler.js 的顶部。只要代码中执行 require('jscrambler') 或运行其 CLI,恶意二进制就会被投放。
这一步的意图很明确:绕过那些只检查 preinstall/postinstall 脚本的安全扫描器,也让 npm install --ignore-scripts 彻底失效。SafeDep 指出这两个版本至今未被打上 deprecated 标记。
狩猎清单:AI 编码工具首次成为明确靶标
Socket.dev 的研究团队通过破解 ChaCha20-Poly1305 单字符串加密,从二进制中恢复了约 2,400 个明文字符串。解密的字符串揭示了一份令开发者不安的目标清单:
AI 编码工具:Claude Desktop 的 claude_desktop_config.json、Cursor 的 mcp.json、Windsurf 的 mcp_config.json、VS Code 和 VS Code Insiders 的 settings.json 和 .mcp.json、Zed 的 settings.json 和 context_servers。攻击者还搜索 opencode 和 Factory 的配置文件。
这些文件存储的不是普通数据。以 Cursor 和 Claude Desktop 为例,它们的 MCP 服务器配置通常包含云服务的 API 密钥、数据库连接凭证和 Git 访问令牌。一个 AI 编码代理通常拥有读取代码库、执行命令、调用云 API 的权限——这些配置文件就是访问这些权限的"钥匙串"。
JFrog 的进一步分析发现,恶意程序的目标范围远超 AI 工具本身:它还扫描 WireGuard 和 OpenVPN 配置文件、IPsec 密钥、1Password 和 Bitwarden 密码管理器保险库,以及——特别值得注意——Metasploit、Sliver、Havoc、Mythic、Covenant 等红队框架的安装目录。后一组目标表明攻击者刻意瞄准了安全研究人员和渗透测试人员的工作站。
攻击者怎么进来的:一场三小时的拉锯战
Jscrambler 在事后公告中确认,攻击者使用一个泄露的 npm 发布凭据完成了所有恶意发布。GitHub 仓库中不存在任何恶意版本的 commit、tag 或 PR——攻击者绕过了正常的发布流程,直接向 npm registry 推送。
SafeDep 还原的时间线显示了一场激烈的攻防拉锯:
| 时间(UTC) | 版本 | 状态 |
|---|---|---|
| 15:12 | 8.14.0 | 恶意,preinstall 投放器 |
| 17:07 | 8.15.0 | 干净版本,维护者第一次修复 |
| 17:26 | 8.16.0 | 恶意,仅 19 分钟后相同载荷重新发布 |
| 17:41 | 8.17.0 | 恶意,新增 pnpm allowScripts 绕过 |
| 17:46 | 8.18.0 | 恶意,投放器移至 require() 时触发 |
| 17:53 | 8.20.0 | 恶意,与 8.18.0 字节一致 |
| 18:12 | 8.22.0 | 干净版本,当前 latest |
攻击者在维护者发布 8.15.0 修复版本后仅 19 分钟就用 8.16.0 重新上线。这说明凭据被完全控制,单纯的版本回滚无法阻断攻击——攻击者仍然可以在任何时刻重新发布。
8.18.0 和 8.20.0 还包含一个反常的自我依赖声明("jscrambler": "^8.17.0"),一个包在自身 dependencies 中列出自己没有任何合法用途,但可能在某些安装拓扑中引入被污染的依赖版本。
Jscrambler 官方统计受影响版本的下载量约为 1,479 次,所有恶意下载已被 npm 移除。但考虑到包周下载量约 15,800 次,且开发依赖和 CI 环境中可能存在的缓存和锁定文件,实际影响范围可能更大。
载荷的能力边界:不只是窃密
所有五家安全研究机构——Socket.dev、SafeDep、StepSecurity、JFrog 和 The Hacker News——的独立分析在关键点上相互印证。但 JFrog 的分析揭示了几个此前的报道未涵盖的能力:
自动传播:Linux 版本载荷包含了完整的 npm 自传播逻辑。它扫描环境变量和 .npmrc 中的 npm 令牌,用令牌向 registry.npmjs.org/-/whoami 验证身份,查询高下载量包,将 setup.mjs 投放脚本注入其 tarball,然后通过原始 HTTP PUT 请求直接向 npm registry 发布——全程不调用本地 npm 客户端,绕过了 CLI 层面的安全检查。
JFrog 将此恶意软件族命名为 IronWorm,追溯至一个月前首次记录的变种,并将其归入 Shai-Hulud 攻击谱系。
双层外泄路径:控制指令和元数据通过自带的 Tor 客户端加密传输——载荷会从 archive.torproject.org 下载 Tor Expert Bundle,自行启动 Tor 进程,直接进行 SOCKS5 协商。但批量窃取的数据却通过 rustls 建立直连 HTTPS 上传到 temp.sh(一个公共文件分享服务)。JFrog 指出这是一次严重的操作安全失误:直连暴露了受害者的真实 IP,而敏感数据被暂存在第三方服务上。
内核级能力:Linux 二进制动态链接 libbpf.so.1,导入 bpf_object__open_mem 等函数,可以直接从内存加载 eBPF 程序到内核。StepSecurity 和 SafeDep 均标记了此能力。eBPF 字节码嵌入在二进制中,不经文件系统直接加载,使得传统的文件扫描和基于符号表的检测失效。该 eBPF 程序的具体行为仍在分析中。
持久化机制:Windows 上创建隐藏的计划任务,每失败一次每分钟重启一次,最多重试 999 次;macOS 上部署 LaunchAgent,设置 RunAtLoad 和 KeepAlive,30 秒重试间隔;Linux 上使用 systemd 服务和 crontab。
npm 12 的时间巧合
npm 12 于 7 月 8 日发布——恰好在此次攻击三天前。npm 12 的关键变化是默认禁用依赖安装脚本。在 npm 12 上,此类 preinstall 钩子不会自动执行,除非用户明确批准。
但 8.14.0 至今仍在 npm 上可用,只是被标记为 deprecated。任何固定到该版本的 lockfile 或命令会在旧版 npm 客户端上自动执行安装脚本。而 8.18.0 和 8.20.0——投放逻辑已移入模块主代码的版本——甚至不受安装脚本禁用策略的约束。
这意味着什么
此次攻击最值得关注的变化不是恶意软件的技术复杂度——虽然 eBPF 注入、ChaCha20 加密、自传播等技术确实令人侧目——而是攻击面的转移。
过去,npm 供应链攻击以窃取 CI/CD 密钥和云凭据为主。攻击者默认有价值的密钥存在于环境变量或 .env 文件中。jscrambler 事件表明,有攻击者已经认识到 AI 编码工具的配置文件是一种更高效的目标:一个文件同时包含多个云服务的 API 密钥、MCP 服务器的访问凭据、以及通过 AI 代理权限间接获得的代码库和基础设施访问权。
Cursor、Claude Desktop、Windsurf 等工具过去两年迅速从实验性产品变成开发者日常工作环境的核心组件。但它们的安全模型仍然基于一个假设:运行在可信的本地环境。当开发者通过 npm install 无意中执行恶意代码时,这个假设被打破——而 AI 工具的"全面访问"设计哲学,在受到攻击时立刻变成了攻击者的"全面访问"。
Jscrambler 本身是一家代码保护和供应链安全公司。它的官方 CLI 工具成为供应链攻击的载体,本身就构成了一个黑色幽默:保护代码的人被用来攻击写代码的人。