前言
2026年6月1日,Socket 安全团队披露了一起代号为Miasma的供应链攻击事件:攻击者向 Red Hat 的@redhat-cloud-services命名空间下至少 4 个 npm 包注入了恶意代码。任何开发者执行npm install时,恶意代码即触发——窃取本机凭证和密钥,并投放自传播蠕虫。
这不是孤立事件。同一天,Aikido Security 披露了 npm 包codexui-android(周下载量超过 29,000 次)在过去一个月内持续静默窃取 OpenAI Codex 认证令牌。
两起事件叠加释放了一个明确信号:开发者本机的凭证和密钥已经成为供应链攻击者的首要目标——攻击者不需要攻陷服务器,一条npm install命令就足够了。
一、Miasma 攻击的技术拆解
1.1 受影响范围
| 字段 | 内容 |
|---|---|
| 攻击代号 | Miasma |
| 攻击分类 | Mini Shai-Hulud 系列(供应链凭证窃取蠕虫) |
| 受影响包 | @redhat-cloud-services/vulnerabilities-client、@redhat-cloud-services/tsc-transform-imports、@redhat-cloud-services/topological-inventory-client、@redhat-cloud-services/sources-client等 |
| 恶意行为 | 安装时执行 → 凭证收集 → CI/CD 攻击 → 加密外传 → 自传播 |
| 归因 | 无法确定(TeamPCP 已开源 Shai-Hulud 工具,模仿者众多) |
1.2 攻击链:npm install 为什么会泄露你的凭证
Miasma 系列攻击的核心战术是install-time execution(安装时执行)。对于 npm 包而言,postinstall脚本在npm install完成后自动以当前用户权限运行——这意味着它能接触到开发机上的所有用户态文件:
1. 开发者执行 npm install @redhat-cloud-services/vulnerabilities-client 2. postinstall 恶意脚本自动触发 3. 扫描目标目录: ~/.npmrc (npm 注册表认证令牌) ~/.git-credentials (Git 凭据) ~/.aws/credentials (AWS IAM 密钥) .env / .env.local / .env.production ~/.ssh/id_rsa (SSH 私钥) ~/.config/ (各工具配置文件) 4. 收集的凭据打包加密 → 上传至攻击者 C2 5. 投放蠕虫组件:扫描本地 Git 仓库 → 修改 package.json → 等待下次 publish1.3 为什么这比传统攻击更危险
传统 npm 供应链攻击通常是一次性的:攻击者发布恶意包 → 少数开发者上当 → 包被下架 → 攻击结束。但 Miasma 的不同在于蠕虫自传播:
- 攻击者不需要持续运营恶意包
- 被感染的开发者如果发布了受到蠕虫修改的包,下游使用者 npm install 后同样被感染
- 这种传播方式让攻击呈指数扩散——最初只需要感染一个活跃维护者,最终可以污染整个依赖树
1.4 与 Shai-Hulud 的关系
Miasma 被归类为 Mini Shai-Hulud。Shai-Hulud 是 2025 年下半年爆发的供应链蠕虫家族,核心战术为:安装时执行 → 凭证收集 → CI/CD 攻击 → 加密外传 → 下游传播。TeamPCP 组织已将 Shai-Hulud 的完整工具链开源,这使得模仿攻击的门槛被拉到了极低水平——任何有基础 npm 操作经验的攻击者都可以复现这一攻击模式。
二、codexui-android:合法包的缓慢投毒
Aikido Security 研究员 Charlie Eriksen 在同一天披露了codexui-android包的令牌窃取行为。这个案例比 Miasma 更值得警惕,因为它走的不是"发布一个恶意包等受害者安装"的粗暴模式,而是渗透合法包、缓慢投毒。
2.1 攻击特征
- npm 包周下载量超过 29,000 次,关联的 GitHub 仓库保持干净(无恶意代码痕迹)
- 恶意代码在包发布约一个月后被注入——攻击者拥有包的发布权限
- 每次调用都会静默将 Codex 认证令牌外传到攻击者控制的服务器
- 攻击持续了至少一个月才被发现
2.2 合法包投毒 vs 冒牌包
┌────────────────────────────────────────────────────┐ │ 两种 npm 供应链攻击模式对比 │ ├──────────────┬─────────────────┬───────────────────┤ │ 战术模式 │ typosquat │ 合法包渗透 │ │ 典型行为 │ 注册名称相似的包 │ 获取合法包发布权限│ │ 发现难度 │ 中(包名可疑) │ 高(仓库干净) │ │ 影响面 │ 低(少数人拼错名)│ 高(已有用户量) │ │ 案例 │ 常规投毒攻击 │ codexui-android │ └──────────────┴─────────────────┴───────────────────┘codexui-android 的教训是:npm 安全审计不能只看包的初始版本是否干净——持续监控包的行为变化是基础要求。如果团队在 CI/CD 中只做了npm audit而没有运行时行为检测,完全无法发现这类缓慢投毒。
三、开发者机器的凭据面:比你想的更大
Miasma 和 codexui-android 的目标不是服务器,而是开发者的本地机器。为什么?因为开发机上往往存储着整个组织最有价值的凭据集合:
- npm 令牌:有 write 权限的令牌可以发布新版本(或被蠕虫用于传播)
- Git 凭据:能访问私有仓库(等于拿到源代码)
- 云厂商密钥(AWS AK/SK、阿里云 AccessKey):直接控制云基础设施
.env文件:数据库密码、API Key、加密盐值- SSH 私钥:直连生产服务器的跳板
一个npm install命令就把所有这些暴露给了攻击者。这不是理论风险——Red Hat 的 npm 命名空间被攻陷这个事实本身就说明即使是最受信赖的包来源也不是绝对安全的。
四、工程防护:从"相信 npm install"到"凭据零落地"
4.1 原则一:开发机上不应该存在持久化凭据
对抗 Miasma 这类攻击的关键不是"检测恶意包"(这个永远落后一步),而是让开发机上根本没有值得窃取的凭据:
# 旧做法:开发者在本地 .env 里直接写生产凭据# DATABASE_PASSWORD=prodpass123 ← 被 postinstall 脚本一锅端# 新做法:动态凭据注入# 应用启动时从凭据管理平台拉取临时凭据,变量仅在内存中存在importos,subprocess,jsondefget_db_credential():"""从凭据管理系统获取临时数据库凭据,TTL=1小时"""result=subprocess.run(["credential-cli","get","--path","prod/postgres/write"],capture_output=True,text=True)cred=json.loads(result.stdout)# 凭据仅在内存中,写入环境变量但从不落盘os.environ["DB_USER"]=cred["username"]os.environ["DB_PASS"]=cred["password"]returncred4.2 原则二:CI/CD 凭据走注入,不走代码仓库
GitHub Actions / GitLab CI 的 Secrets 机制本身是安全的,但很多团队为了方便把密钥直接编码在.github/workflows/*.yml或.gitlab-ci.yml中。Miasma 蠕虫恰恰会扫描这些文件:
# ❌ 错误做法:密钥硬编码在 CI 配置中env:AWS_ACCESS_KEY_ID:AKIAIOSFODNN7EXAMPLE# ← 被蠕虫一锅端AWS_SECRET_ACCESS_KEY:wJalrXUtnFEMI/K7MDENG# ← 同上# ✅ 正确做法:从凭据管理系统动态获取env:# 不写具体值,运行时由 SDK 从 KSP 拉取AWS_ACCESS_KEY_ID:""AWS_SECRET_ACCESS_KEY:""更好的做法是:CI/CD pipeline 启动时通过 SDK 从密钥管理系统获取临时凭据,pipeline 结束后凭据自动失效。这样即使蠕虫窃取了 CI/CD 的运行时环境变量,拿到的也是几小时后自动过期的临时凭据。
4.3 原则三:npm 依赖的行为基线监控
对抗 codexui-android 式缓慢投毒,需要从"单次安全审计"切换到"持续行为监控":
// npm 依赖行为基线检测的伪代码逻辑constBASELINE={// 基线定义:合法包不应该有的行为filesystemAccess:["~/.ssh","~/.aws","~/.git-credentials",".env*"],networkRequests:["npm install"阶段不允许外连],childProcess:["不允许在 postinstall 中执行 curl/wget/nc"]};functionauditPostinstallScripts(packageName){constmanifest=readPackageManifest(packageName);constscripts=manifest.scripts||{};for(const[hook,command]ofObject.entries(scripts)){if(hook==='postinstall'||hook==='preinstall'){// 检查命令是否存在可疑关键词for(constsuspiciousof['curl','wget','nc','/dev/tcp','eval']){if(command.includes(suspicious)){thrownewError(`高危:${packageName}的${hook}脚本包含 {suspicious},已阻止安装`);}}}}}4.4 原则四:开发环境隔离
如果开发项目依赖第三方包,将项目开发环境与日常使用的机器环境隔离是一条低成本但高效的措施:
- 使用 Dev Container 或虚拟机作为开发环境,仅挂载必要的项目目录
- 宿主机的
~/.ssh、~/.aws等敏感目录不挂载到开发容器中 - 即使 npm 包在容器内执行恶意代码,也无法接触到宿主机的凭据
五、npm 生态的结构性弱点
Miasma 和 codexui-android 不只是两个安全事件,它们暴露了 npm 生态的三个结构性弱点:
1. postinstall 执行权限过大。npm 的postinstallhook 在设计上拥有当前用户的所有权限——它不是一个沙箱环境。这意味任何npm install都是对执行用户的完全信任授权。
2. 包发布者身份验证薄弱。codexui-android 案例表明,获得一个合法包的发布权限(通过社工、令牌泄露或账号劫持)的攻击者可以在 GitHub 仓库完全干净的情况下注入恶意代码。
3. "信任知名来源"策略失效。Red Hat 是全世界最受信任的开源组织之一,但它的 npm 命名空间仍然被攻陷。这推翻了一个长期假设:来自知名组织/知名维护者的包是安全的一一从现在开始,这个假设不再成立。
六、总结
Miasma 和 codexui-android 给开发者社区上了三堂课:
npm install 是一个信任动作,而不只是"装个依赖"。它赋予了包以你当前用户的全部权限去执行任意代码。你需要像审查生产环境 SSH 权限一样审查安装行为。
凭据不应该存在于开发机上。动态凭据注入、临时凭据、开发环境隔离——这些措施的目标不是"让 npm 更安全",而是"让攻击者在开发机上找不到有价值的东西可偷"。
安全审计必须从"一次性的"变成"持续的"。codexui-android 在投毒后才被发现,而且持续了整整一个月——这一个月里,29,000 次/周的下载量意味着数万开发者的 Codex 令牌被窃取。持续的行为基线监控是唯一的防守方式。
💬 话题讨论:你们团队的安全策略里,开发环境的凭据是怎么管理的?有没有遇到过 npm 包的审计问题?欢迎评论区聊聊。