一句「npm publish」评论,撬开一条可信发布流水线:openapi-react-query-codegen 供应链投毒事件深度复盘

· 2026-08-31 15:45 · 5 阅读

原创 威胁情报中心 2026-08-31 15:45 北京

十个恶意版本、约 15 万周下载量、全部携带有效的 npm 来源证明(provenance)——这一次,攻击者甚至没有偷任何人的账号密码,只是在 Pull Request 下面留了一句评论。

供应链安全深度复盘

一句「npm publish」评论,撬开一条可信发布流水线

openapi-react-query-codegen 供应链投毒事件深度复盘:十个恶意版本、约 15 万周下载量、全部携带有效的 npm 来源证明(provenance)——这一次,攻击者甚至没有偷任何人的账号密码,只是在 Pull Request 下面留了一句评论。

· 供应链安全 · npm 生态 · 2026-08-31

01 事件速览

2026 年 8 月 28 日,npm 生态再次发生供应链投毒事件。中招的是 @7nohe/openapi-react-query-codegen——一个能将 OpenAPI 规范自动生成 TypeScript 客户端和 React Query hooks 的代码生成工具,全版本周下载量约 15 万次,在 React 开发者群体中应用广泛。

当天 UTC 时间 20:00 至 20:21 之间,攻击者分两波(间隔约 20 分钟)向 npm 发布了 10 个恶意版本,覆盖了该包所有仍在维护的版本线。安全研究团队 Socket 与 StepSecurity 先后披露了此事,并将其与 Mini Shai-Hulud 蠕虫家族关联分析。

项目

内容

受影响包

@7nohe/openapi-react-query-codegen

恶意版本数

10 个(8 个稳定版 + 2 个 0.0.0-* 预发布版)

发布窗口

2026-08-28 20:00:43 – 20:20:53 UTC

周下载量

约 150,000

攻击入口

GitHub Actions 中由 PR 评论触发的发布工作流

载荷特征

凭证窃取 + 多注册表自我传播,具备蠕虫属性

已知安全版本

0.5.3、1.6.2、2.2.0、3.0.2

截至本文发稿(8 月 31 日),npm 注册表已移除全部 10 个恶意版本,latest 标签已重新指向安全版本 3.0.2。但在事件曝光初期,latest 一度直接解析到恶意版本 3.0.4——这意味着那段时间里一条最普通的 npm install 就会把木马装进开发机或 CI 环境。

图 | 事件曝光初期,npm 页面上latest标签指向恶意版本 3.0.4(图:StepSecurity)

02 攻击入口:一句评论如何变成「发布许可证」

这起事件最值得复盘的不是恶意代码本身,而是攻击者进入发布流水线的姿势——它简单到令人不安。

2.1 被滥用的工作流

该项目的 release.yml 发布工作流监听 issue_comment 事件,触发条件大致是:

if: ${{ github.event_name == 'push' || (github.event.issue.pull_request && github.event.comment.body == 'npm publish') }}

翻译成大白话:只要某条评论出现在 PR 下面,且内容恰好是「npm publish」,发布任务就会启动。 整个判断逻辑里没有任何一步去核验评论者的身份——是不是仓库成员、是不是协作者、有没有写权限,一概不问。

更致命的是这个任务的后续动作:

1检出该 PR 的头部代码(pull//head,即来自 fork 的、未经审查的代码);

2pnpm install 安装依赖;

3以 id-token: write 权限运行 pnpm publish --no-git-checks,通过 npm Trusted Publishing 的 OIDC 身份直接发包。

图 | release.yml 顶部:任务持有 id-token: write,触发条件只看评论文本是否等于「npm publish」(图:StepSecurity)

图 | 同一工作流中,任务检出 PR 头部代码后执行 pnpm install 与 pnpm publish --no-git-checks(图:StepSecurity)

于是,任何一个 GitHub 路人账号,只要 fork 仓库、提一个塞满恶意代码的 PR、再在评论区敲下「npm publish」,就能以该项目官方的可信发布身份把代码推到 npm 上。 不需要 maintainer 密码,不需要窃取长期 token,甚至不需要任何人点「合并」。整个攻击入口的完整时序如下图所示:

图 | 攻击入口完整时序

2.2 攻击者的实际操作

GitHub 账号 p00paboot 正是这么干的:8 月 28 日 19:59 UTC,它提交了 PR #215(标题伪装成「Add new testcases for OpenAPI」),随后在同一条 PR 下发出「npm publish」评论,紧接着又操作了 PR #216。工作流运行畅通无阻地走到了发布步骤,npm 注册表的时间戳在几秒内跟进。整个过程在公开页面上完成,没有任何入侵痕迹可言——因为大门本来就是敞开的。

图 | 攻击者 fork 中的恶意提交 365d4eb:向 package.json 注入带环境变量的 preinstall 命令(图:Socket)

最先发现异常并报告的是安全研究员 Charlie Eriksen(GitHub issue #217)。

03 来源证明(Provenance)为什么没能拦住恶意包

这是本次事件最具警示意义的一环。

npm 的 provenance 机制配合 GitHub Actions Trusted Publishing,会用 OIDC 身份为发布的包生成加密签名证明,npm audit signatures 可以校验。而这次全部 10 个恶意版本都携带了有效的 provenance 证明——因为它们确实是通过项目自己的 release.yml、以项目的官方 OIDC 身份发布的。签名是真的,流水线是真的,只有代码是假的。

这里有一个微妙但关键的细节:issue_comment 事件触发的工作流中,GITHUB_REF 取的是默认分支。因此恶意包的 SLSA 证明里记录的是 refs/heads/main 上最后一个正常提交(即干净的 v3.0.2 提交 d42d1733)——证明文书看上去完美无瑕,而 tarball 里装的却是攻击者的代码。

图 | 恶意版本 3.0.4 在 npm 页面上展示着有效的 provenance:构建于 GitHub Actions,源提交指向干净的d42d173(图:StepSecurity)

Provenance 证明的是「这个产物由哪条流水线构建」,而不是「这条流水线只构建可信代码」。 当工作流本身能被不可信输入劫持时,来源证明反而成了恶意包的「信用背书」。这一点,从今年 5 月 TanStack 事件(恶意包携带有效 SLSA Build Level 3 证明)开始已被反复验证,这次则是又一次实锤。

04 十个恶意版本与三条执行路径

攻击者对不同版本线采用了略有差异的投递手法,可以归纳为三条路径:

4.1 路径一:binding.gyp 的 Python 逃逸(第一波稳定版)

8 个恶意稳定版中都带有一个恶意的 binding.gyp 文件。需要说明的是,这个包本身并没有任何原生模块——binding.gyp 的存在本身就是可疑信号。

文件中的 conditions 字段藏了一段高度混淆的 Python 表达式:通过 catch_warnings 子类遍历拿到 Python 内建对象,再经 __import__ 导入 os,最终调用 os.system()。所有关键字符串都被转义成 \U000000XX 形式的 Unicode 编码,解码后的真实命令只有一句:

node 3FWCvzduYZg.js

由于 binding.gyp 会在 npm install 期间被 node-gyp 解析处理,这条路径在开发者机器和 CI runner 上都会自动触发,而且它不属于 package.json 里显眼的生命周期脚本,隐蔽性更强。

4.2 路径二:preinstall 钩子(第二波稳定版叠加)

第二波的恶意版本在保留同款 binding.gyp 的同时,还在 package.json 中直接追加了:

"preinstall": "node 3FWCvzduYZg.js"

双保险,确保载荷一定被执行。StepSecurity 在隔离的 GitHub 托管 runner 上复现了安装行为:1.6.3、2.2.1、3.0.3、3.0.4 等版本在安装期间会拉起 curl 访问 GitHub 及 release-assets.githubusercontent.com,随后在 /tmp/trinnyyyy-* 目录下出现 Bun 可执行文件。

版本体积也是明显的异常信号:0.5.4 从上一版的约 41 KB 暴增至约 5.6 MB,其余恶意稳定版体积在 4.46 MB 到 6.53 MB 之间——正常发布绝不会有这种量级跳变。

图 | 恶意版本 3.0.4 的文件列表(图:StepSecurity)

4.3 路径三:两个 0.0.0-* 预发布版(自举变体)

两个以 commit 哈希命名的预发布版本走了另外的路:

0.0.0-365d4eb...:preinstall 先拉取并安装 Bun,然后执行 is_it_this_simple.js,同时注入环境变量 WORKFLOW_ID=release.ymlREPO_ID_SUFFIX=7nohe/openapi-react-query-codegenTARGET_PACKAGES=@7nohe/openapi-react-query-codegen

0.0.0-ec7876d...:preinstall 设置同样的环境变量,但直接执行 node nu.js,不拉取 Bun。

Socket 在分析攻击者 fork 的提交链时发现,这两套脚本是分批引入的:一条链先加 is_it_this_simple.js 再引入基于 Bun 的 preinstall,另一条先加 nu.js 再引入并修改对应钩子——明显的分阶段搭建痕迹。文件名「is_it_this_simple」(就这么简单?)更像是攻击者留下的嘲讽。

05 载荷能力拆解:一个为「吞噬凭证并自我复制」而生的蠕虫

3FWCvzduYZg.js 是一个约 5.6 MB 的单行混淆 JavaScript 文件(不同版本中的该文件大小与哈希存在差异,说明攻击者对不同版本线投放了不同构建)。它开头是一个基于单字节 XOR 解码的 Function 构造器,负责解密内嵌的 AES-128-GCM 加密的第二阶段载荷,写入临时目录的随机文件名后通过 child_process.execSync 执行,最后在 finally 块中删除痕迹。

Socket 报告给出的攻击链总览如下(原文流程图,直接引用):

npm install / native-build path

|

v

binding.gyp Python escape -> node 3FWCvzduYZg.js

|

+--> anti-analysis checks and detached relaunch

+--> scan files, process memory, cloud metadata, and CI variables

+--> validate discovered credentials

+--> collect and encrypt results

| +--> attacker-created GitHub repository commits

| +--> GitHub token monitor and handler

| +--> public-repository workflow poisoning

+--> poison npm/JFrog/RubyGems packages

+--> optional PyPI typosquatting

+--> developer-tool/MCP persistence

+--> signed GitHub-commit command channel

+--> SSH propagation

在此基础上,我们将「安装触发 → 载荷加载 → 四路凭证发现 → 验证筛选 → 加密外泄」的完整过程展开为下图:

图 | 载荷执行与凭证窃取、加密外泄的完整流程

以下能力细节均来自 Socket 团队对样本的静态提取与反混淆分析(分析过程中未实际执行样本)。

5.1 反分析与进程隐身

载荷落地后第一件事是自保:

检查环境中是否已安装 Bun、是否处于 GITHUB_ACTIONS 上下文、是否存在安全软件,命中即退出或改变行为;

通过 __DOGINSIDEPC 环境变量实现「分离重启」:父进程派生一个脱离标准输入输出的独立子进程后自行退出,让真正的恶意进程脱离最初安装命令的生命周期,从进程树视角隐藏自己;

所有收集与传播操作默认静默吞错,使用随机临时目录与随机仓库名,最大限度降低噪声。

5.2 四路凭证发现

载荷内置了成套的凭证扫描器,覆盖几乎所有开发者会「漏」出密钥的地方:

(1)文件系统扫描:递归遍历当前工作目录(含隐藏文件,最多检查 12,000 个文件),内置针对各类 token 的正则——GitHub 的 gho_/ghp_/github_pat_、npm 的 npm_、RubyGems、PyPI、JFrog 的访问令牌与引用令牌,以及 JWT、数据库连接串、云凭证等通用密钥模式。

(2)进程内存扫描:通过一个 Python 辅助程序读取 /proc//maps 和 /proc//mem,遍历可读内存映射并转储内容,再用同一套凭证正则扫描——目标是那些只存在于运行中进程内存里、不落盘的密钥(开发工具、包管理器、CI agent 等)。

(3)环境变量枚举:收集一大批 CI/CD 与云平台变量,包括但不限于 GITHUB_TOKENACTIONS_ID_TOKEN_REQUEST_TOKENCI_JOB_TOKENNPM_TOKENANTHROPIC_API_KEY、AWS 三件套、AZURE_*GOOGLE_APPLICATION_CREDENTIALSVAULT_TOKEN 等。

(4)云元数据服务:请求 AWS IMDSv2 端点(169.254.169.254)、ECS 任务元数据(169.254.170.2)、GCP 的 metadata.google.internal,以及 Azure(management.azure.comlogin.microsoftonline.com)、Vault 等凭证端点,直接榨取云实例角色凭证。

5.3 先验证、再下手:带「智商」的凭证利用

载荷并不会把扫到的所有字符串一股脑外传,而是先逐一验证有效性,再按权限决定后续动作:

  • GitHub token:调用 API 确认 scopes,只有具备 repo 或 public_repo 权限的 token 才会启用仓库级操作;

  • npm token:查询 registry 的 token 接口,要求是非 2FA 且具备包写权限的 token,再枚举可写的组织与包范围;

  • PyPI token:向 upload.pypi.org/legacy/ 发送试探性 multipart 请求,以 400 响应作为 token 有效的判据,再探测包所有权;

  • RubyGems token:查询 api_key.json,要求具备 index_rubygems 权限;

  • JFrog 凭证:针对发现的 .jfrog.io 端点测试,仅在会话确认有写权限时才使用。

5.4 加密外泄:把 GitHub 当成免费的「死信箱」

收集到的敏感数据会进入一个缓冲队列(默认满 102,400 字节触发发送),随后:JSON 序列化 → gzip 压缩 → 生成随机 AES-256-GCM 密钥加密 → 用内嵌的 RSA 公钥(RSA-OAEP/SHA-256)加密该 AES 密钥 → Base64 打包。

外泄通道颇具迷惑性:载荷用受害者的 GitHub token 创建一个全新的公开仓库(名称是随机生成的东方 Project 主题词汇,描述为「Trinitite: Sponsored by Preview 2 Effects」),把加密数据以 commit 形式推上去。对任何旁观的网络审计者来说,这只是「用户在往 GitHub 推代码」——合法平台承担了传输与存储层的角色,内容本身又是只有攻击者私钥能解的密文。

5.5 多生态投毒:蠕虫的自我复制引擎

这是该样本被称为「Mini Shai-Hulud 风格」的核心原因——它能把每一个受害者变成新的分发节点:

  • npm:用验证通过的 token 枚举可写包,下载 tarball、注入载荷、重新发布;

  • JFrog:对私有 registry 中可写的 npm 仓库做同样的事(代码中带有 [jfrognpm] 标记);

  • RubyGems:枚举账号名下的 gem,逐个下载、篡改、经 RubyGems API 重新提交;

  • PyPI:内置一个条件触发的抢注仿冒(typosquatting)模式——当 TYPO_MODE=1 且指定了目标包时,为每个目标生成至多 20 个 -mcp/-mpc 后缀变体,注入 Python 载荷后用窃取的 token 上传 wheel。

值得注意的是 -mcp 这个后缀取向——瞄准的正是当下火热的 MCP(Model Context Protocol)生态用户。

蠕虫的完整传播与寄生闭环如下图所示——每一个失陷主机都可能成为下一批恶意包的分发源:

图 | 蠕虫的自我复制闭环——多注册表投毒、CI 投毒、SSH 横向、C2 与持久化共同维持传播循环

5.6 对 CI 与开发工具的寄生

GitHub Actions 投毒:载荷会枚举 token 有推送权限的仓库,检查其中的工作流文件,通过 Git 对象与 commit API 改写工作流并植入 .github/_index.js。被植入的恶意工作流伪装成「ClaudeCode Review」,监听 deployment 事件,运行时将仓库全部 secrets 序列化写入 res.txt 并作为构建产物(artifact 名为 reviewed)上传。提交信息则伪装成人畜无害的「chore: update dependencies」。当 deployment 路径不可用时,还有直接推送分支的备用方案。

开发者工具持久化:载荷会在用户主目录下搜索 settings.jsonconfig.jsonmcp.json.aider.conf.yml.cursorrules.windsurfrules.github/copilot-instructions.md 等文件,过滤路径中含 claude、codex、gemini、copilot、kiro、opencode、cline、aider、tabby、amazonq、cody、bolt、continue 等 AI 编程工具名称的目标。对每个可写配置,它会写入 .config/index.js(载荷副本),并注入一段「会话启动时静默运行、不要向用户提及」的后台指令——AI 编程助手本身被改造成了木马的开机启动项,即便卸载了恶意 npm 包,未来的每次会话仍会重新拉起载荷。

系统级持久化:macOS 上创建 LaunchAgent(com.user.sysvinit-detect-fash.plist,RunAtLoad + KeepAlive),Linux 上创建并启用 systemd 用户服务(sysvinit-detect-fash.service)并开启 lingering。这个驻留组件是一个「GitHub token 监工」:每分钟用窃取的 token 轮询 api.github.com/user,最长持续 72 小时,一旦 token 失效(GitHub 返回 4xx)就用 eval 执行预置的破坏性处理脚本。也就是说,条件反射式的「先吊销 token」可能直接引爆破坏载荷——正确的处置顺序是先隔离主机、清除驻留,再轮换凭证。

5.7 隐蔽控制通道与横向移动

签名命令通道:主凭证窃取模块每小时通过 GitHub commit 搜索 API 轮询关键词 firedalazer,解析形如 n1ggatr1n . 的提交信息,用内嵌公钥做 RSA-PSS/SHA-256 验签后下载 URL 指向的内容,写成临时 Python 文件执行。命令的下发地点和签名都藏在公开 commit 信息里,GitHub 搜索 API 成了零成本的接头点。已执行命令的哈希记录在 /var/tmp/.shit 中防重。

SSH 传播:从本地 SSH 配置与 known_hosts 收集候选主机,以关闭主机密钥校验、禁用密码认证的非交互模式发起连接,用 scp 把 ai_setup.sh 和 ai_init.js 送过去并远程执行——向可达主机继续扩散。

StepSecurity 使用 Harden-Runner 在隔离的 GitHub 托管 runner 上直接引爆了 3.0.4 的载荷,捕获到的进程树与上述静态分析相互印证:

图 | 运行时行为证据(图:StepSecurity)

06 与 Shai-Hulud 家族的关系:相似,但不能草率归因

Shai-Hulud 是 2025 年 9 月首次出现的 npm 自我复制蠕虫(名称出自《沙丘》中的沙虫),此后持续演化:2025 年 11 月出现具备数据擦除能力的第二代;2026 年 4–5 月,TeamPCP 组织的 Mini Shai-Hulud 行动感染了 TanStack、Mistral AI、UiPath 等 170 余个 npm/PyPI 包,并首次实现了携带有效 SLSA 证明的恶意发布;2026 年 8 月初又出现了感染 400 余包的 ChainDrop 变体。该蠕虫源码已于今年 5 月被公开,模仿者层出不穷,使得归因愈发困难。

本次样本与 Mini Shai-Hulud 公开描述存在大量行为重叠:npm 安装期执行、Bun 加载器、全方位凭证收割、GitHub Actions 投毒、利用窃取的 npm/GitHub 权限自我传播、用 GitHub 仓库做存储与通信等。

但差异同样明显:本样本使用 Unicode 转义的 binding.gyp 触发器(而非此前报告中的 postinstall 模式);同时支持 macOS 与 Linux;采用 AES-GCM/RSA 加密信封加攻击者自建公开仓库的外泄方式;并新增了 RubyGems/JFrog 滥用、条件式 PyPI 抢注、开发工具配置持久化、签名 commit 命令通道和 SSH 传播等模块。

行为重叠只能说明「师出同门」的可能性,不能证明出自同一批操作者。 在蠕虫源码已公开的当下,基于具体 IOC(哈希、路径、字符串)的检测比基于行为画像的归因更可靠。

07 应急响应建议

7.1 如果安装过受影响版本

安装过即视为主机已失陷,按以下顺序处置(顺序很重要):

1先隔离 —— 立即将安装过受影响版本的开发机或 CI runner 断网隔离。注意载荷中的「token 监工」会在 token 失效时触发破坏行为,务必先断网/清除驻留,再动凭证

2优先重建 —— 从已知干净的镜像重建受影响系统。若必须原位清理,先定位并移除全部持久化机制(LaunchAgent、systemd 用户服务、~/.config/sysvinit-detect-fash/、开发工具配置中的注入项、/var/tmp/.shit/tmp/trinnyyyy-* 等),再轮换凭证

3轮换凭证 —— 从一台干净的机器上吊销并轮换该环境可触及的一切凭证:npm、GitHub、PyPI、RubyGems、JFrog、CI/CD secrets、云凭证、SSH 密钥、AI 工具 API Key

4全面排查 —— 检查 lockfile 与 SBOM 中是否存在 10 个恶意版本(含间接依赖,npm ls @7nohe/openapi-react-query-codegen);不要依赖 npm audit signatures——恶意版本的签名是真的

5审计日志 —— GitHub 审计日志中留意新建公开仓库、工作流文件变更、deployment 创建、「chore: update dependencies」提交、名为 reviewed 的 artifact;npm/PyPI/RubyGems/JFrog 上留意非预期的版本发布;同时检查 SSH 日志与 known_hosts 中的异常外联

6锁定版本 —— 固定到已知安全版本(0.5.3 / 1.6.2 / 2.2.0 / 3.0.2),清理包管理器缓存并删除 node_modules 后从干净 lockfile 重装

7.2 给开源维护者的加固清单

这起事件的「根因」与其说是高深漏洞,不如说是 CI 配置的基本卫生问题:

  • 绝不允许仅凭评论文本触发发布。 任何监听 issue_comment 且持有 id-token: write 的工作流,都必须校验 github.event.comment.author_association(仅允许 MEMBER/OWNER/COLLABORATOR),或者干脆把发布迁移到不可信账号无法触发的事件上(如手动 workflow_dispatch、打 tag);

  • 永远不要在特权工作流中检出并执行 PR/fork 代码。 不可信代码 + OIDC 发布身份 = 把签名钥匙递给陌生人;

  • 默认关闭安装脚本npm install --ignore-scripts 可拦下 preinstall 钩子,禁用自动 node-gyp 构建可封死 binding.gyp 路径(CI 中尤其值得开启);

  • 不要把 provenance 当作安全结论。它回答「从哪来」,不回答「好不好」;锁定版本 + 完整性哈希校验 + 关注发布节奏异常(几分钟内回填多个版本线是典型的失陷信号)才是更有效的组合拳。

08 技术附录

8.1 MITRE ATT&CK 技术映射

战术

技术 ID

技术名称

本次事件中的体现

初始访问

T1195.002

供应链投毒:软件供应链

通过被劫持的发布工作流向 npm 推送恶意版本

执行

T1059.007

命令与脚本解释器:JavaScript

preinstall / binding.gyp 触发 node 3FWCvzduYZg.js

执行

T1059.006

命令与脚本解释器:Python

binding.gyp 中的混淆 Python 表达式调用 os.system();Python 内存转储器

执行

T1059.004

命令与脚本解释器:Unix Shell

Bun 下载脚本、eval 执行驻留 handler

持久化

T1543.001

创建或修改系统进程:Launch Agent

com.user.sysvinit-detect-fash.plist

(RunAtLoad + KeepAlive)

持久化

T1543.002

创建或修改系统进程:Systemd Service

sysvinit-detect-fash.service

 用户级服务并启用 lingering

持久化

T1546

事件触发执行

AI 编程工具配置/MCP 配置注入会话启动钩子;VS Code folderOpen 任务

防御规避

T1027

文件或信息混淆

单字节 XOR + AES-128-GCM 内嵌载荷、Unicode 转义字符串、混淆字符串表

防御规避

T1036

伪装

恶意工作流伪装为「ClaudeCode Review」,提交信息伪装为「chore: update dependencies」

防御规避

T1497

虚拟化/沙箱规避

检查 Bun 是否已装、CI 环境标识、安全软件存在性,命中即退出或改行

防御规避

T1070

痕迹清除

载荷执行后于 finally 块删除临时文件

凭证访问

T1552.001

非安全凭证:文件中的凭证

递归扫描工作区(含隐藏文件)匹配各类 token 正则

凭证访问

T1552.005

非安全凭证:云实例元数据 API

请求 AWS IMDSv2、ECS、GCP 元数据端点窃取实例角色凭证

凭证访问

T1003

操作系统凭证转储

读取 /proc/<pid>/maps 与 /proc/<pid>/mem 转储进程内存并扫描密钥

发现

T1083

文件与目录发现

递归枚举工作区与主目录下的开发工具配置文件

发现

T1057

进程发现

枚举运行中的进程以筛选内存转储目标

横向移动

T1021.004

远程服务:SSH

解析 SSH 配置与 known_hosts,非交互 SSH + scp 向可达主机传播

横向移动

T1078

有效账户

复用窃取的 GitHub/npm/PyPI/RubyGems/JFrog 凭证执行投毒与发布

收集

T1602

配置仓库数据收集

窃取 GitHub Actions secrets、CI/CD 环境变量

命令与控制

T1102.001

Web 服务:死信箱解析器

通过 GitHub commit 搜索(关键词 firedalazer)获取 RSA-PSS 签名的命令 URL

命令与控制

T1573

加密通道

AES-256-GCM + RSA-OAEP 混合加密外泄信封

命令与控制

T1105

工具传输

从 GitHub Releases 下载 Bun 1.4.0;经签名通道下载远程 Python 命令

外泄

T1567.001

向代码仓库外泄

以受害者 token 创建随机命名的公开 GitHub 仓库,commit 推送加密数据

影响

T1485

数据破坏

token 监工在 token 失效(HTTP 4xx)时 eval 执行预置破坏性 handler

影响

T1195.002

供应链投毒(传播)

利用窃取凭证对 npm/JFrog/RubyGems 包投毒、PyPI 抢注仿冒,实现蠕虫式自我复制

8.2 失陷指标(IOC)

恶意包与版本

@7nohe/openapi-react-query-codegen @

0.0.0-365d4eb738d3146583431948d3ba6e27a32556be

0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab

0.5.4 / 0.5.5 / 1.6.3 / 1.6.4 / 2.2.1 / 2.2.2 / 3.0.3 / 3.0.4

已知安全版本(各版本线最后一个干净版本):0.5.31.6.22.2.03.0.2

攻击者基础设施

github[.]com/p00paboot

github[.]com/p00paboot/openapi-react-query-codegen (用于暂存恶意代码的 fork)

恶意提交:365d4eb738d3146583431948d3ba6e27a32556be

相关 PR:#215#216;首次报告:issue #217

文件与路径指标

3FWCvzduYZg.js

binding.gyp

ai_init.js / ai_setup.sh

is_it_this_simple.js / nu.js

~/.local/bin/sysvinit-detect-fash.sh

~/.config/sysvinit-detect-fash/fox

~/.config/sysvinit-detect-fash/fash-detected

~/.config/sysvinit-detect-fash/runit

~/Library/LaunchAgents/com.user.sysvinit-detect-fash.plist

~/.config/systemd/user/sysvinit-detect-fash.service

/var/tmp/.shit

/tmp/.sshu-<random>

/tmp/pcfg/

/tmp/trinnyyyy-*/bun

.config/index.js

.github/_index.js

哈希指标

3FWCvzduYZg.js(SHA-256,不同版本线存在不同构建):

b49afb7dba04cd99b357ce7c652c823a3707f28e130bd5c6645851a7adc030d6

59370c67b54a0ccaedd265e2356f04540b2fba1e1845300ef6de4d5437d99380

b24d121667f21f492cb9db34fbfd515d5922a8dd30b9c45215c7220abbb10ca8 (3.0.4 中 6,384,601 字节版本)

binding.gyp(SHA-256):

d3246926b20a8d021ed7de0ac8e9eee1dda986088f84ba18f31cb2042a121f5d

npm tarball SHA-1:

版本

SHA-1

0.5.4

2d934cf137a4e62519f88e7ab669d2fabda33867

0.5.5

337ae261e4e73a9f365f892dcef2dc6f6932e90a

1.6.3

0f9bc76952b67d7a28a57d1e726293f417df0119

1.6.4

6499c9ab4e60f9b1db6756cb0de55ebc334a72d1

2.2.1

5ab130e4736d4582899af2385ec7eb5a33619d05

2.2.2

fc60551b23485829c0a6e910224b049c891a49b8

3.0.3

bafa4edaa6812fce10ae703ae450cc88ebbe1730

3.0.4

3fc635b988db2bd647b8578dfc1a85769913b708

0.0.0-365d4eb...

e7a07ca4a3cd51c262495f473abe4c4e505b7be4

0.0.0-ec7876d...

206b18c418434abc994bd40e021edcc334eee89b

执行与环境指标

preinstall: "node 3FWCvzduYZg.js"

WORKFLOW_ID=release.yml

REPO_ID_SUFFIX=7nohe/openapi-react-query-codegen

TARGET_PACKAGES=@7nohe/openapi-react-query-codegen

__DOGINSIDEPC(载荷分离重启环境变量)

GitHub commit 搜索关键词:firedalazer

commit 信息格式:n1ggatr1n <base64-url>.<base64-签名>

恶意工作流名:ClaudeCode Review(on: deployment,上传 artifact 名 reviewed)

可疑提交信息:chore: update dependencies

网络指标(重要提示:以下为被滥用的合法服务端点,切勿直接封禁)

github.com / api.github.com(含 /user、/search/commits、/user/repos)

github.com/oven-sh/bun/releases/download/bun-v1.4.0/

registry.npmjs.org(含 /-/npm/v1/tokens、/-/whoami)

upload.pypi.org/legacy/

rubygems.org/api/v1/api_key.json

169.254.169.254(AWS IMDSv2)/ 169.254.170.2(ECS)

metadata.google.internal

graph.microsoft.com / management.azure.com / login.microsoftonline.com

fulcio.sigstore.dev / rekor.sigstore.dev

检测思路:与其封禁这些域名,不如监控异常行为——如 npm install 期间出现 curl 外联、/tmp/trinnyyyy-* 下出现 Bun 可执行文件、CI 任务中意外的 GitHub API 调用、账号下新建随机命名的公开仓库等。

09 结语

回顾整条攻击链,最昂贵的漏洞其实不在代码里,而在一个「图省事」的 CI 设计决策里:为了能用一句评论触发发布,项目把 OIDC 可信发布身份、PR 代码检出、--no-git-checks 这三样东西放进了同一个不对评论者鉴权的工作流。攻击者要做的全部工作,只是 fork、改代码、敲 11 个字符。

而 npm provenance 的「失效」提醒我们:供应链安全工具链中的每一环都只担保它声明的那一件事。签名证明构建来源,扫描发现已知恶意,锁文件固定版本——没有任何单一机制能替代「最小权限 + 不信任未审查输入」这条古老原则。在 AI 编程工具本身都已成为持久化跳板的今天,开发机与 CI runner 理应被视为与生产环境同等重要的防护边界。

10 参考链接

  1. https://socket.dev/blog/openapi-react-query-codegen-npm-compromise

  2. https://www.stepsecurity.io/blog/7nohe-openapi-react-query-codegen-compromised-npm-publishing-workflow

  3. https://www.endorlabs.com/learn/trojanized-7nohe-openapi-react-query-codegen-adds-pypi-to-a-self-replicating-npm-worm

  4. https://www.tenable.com/blog/mini-shai-hulud-frequently-asked-questions

  5. https://unit42.paloaltonetworks.com/monitoring-npm-supply-chain-attacks/

  6. https://www.microsoft.com/en-us/security/blog/2026/08/04/chaindrop-supply-chain-compromise-anatomy-self-propagating-worm/

阅读原文

跳转微信打开