FractalRecursion/shai-hulud-guard
GitHub: FractalRecursion/shai-hulud-guard
一款纯 Python 标准库实现的跨平台 CLI 工具,用于检测、清除和预防针对 npm 与 PyPI 生态的 Shai-Hulud 供应链蠕虫。
Stars: 3 | Forks: 0
# shai_hulud_guard
[](https://github.com/FractalRecursion/shai-hulud-guard/actions/workflows/ci.yml) [](https://github.com/FractalRecursion/shai-hulud-guard/actions/workflows/codeql.yml) [](https://securityscorecards.dev/viewer/?uri=github.com/FractalRecursion/shai-hulud-guard) [](https://github.com/FractalRecursion/shai-hulud-guard/releases/latest)
[](LICENSE) [](https://www.python.org/) [](pyproject.toml) [](tests/)
## TL;DR
```
python shai_hulud_guard.py --check # pre-install npm risk check
python shai_hulud_guard.py --check-pypi # pre-install PyPI risk check
python shai_hulud_guard.py --scan --path . # scan an existing project
python shai_hulud_guard.py --patch --path . # generate per-case remediation scripts
python shai_hulud_guard.py --protect --path . # install proactive defences (reversible)
python shai_hulud_guard.py --unprotect --path . # remove everything --protect installed
python shai_hulud_guard.py --self-test # 6 assertions, synthetic infection roundtrip
python shai_hulud_guard.py --json --scan --path . # machine-readable output for CI / LLM
```
详细文档:
- **[docs/THREAT_MODEL.md](docs/THREAT_MODEL.md)** — 将 Wave 1-5 攻击链逐行映射到防御功能。
- **[docs/DESIGN.md](docs/DESIGN.md)** — 不变性、权衡和非目标。
- **[docs/JSON_SCHEMA.md](docs/JSON_SCHEMA.md)** — 包含可直接粘贴给 LLM 的示例的 `--json` 输出 schema。
- **[BENCHMARKS.md](BENCHMARKS.md)** — 在实时 registry 前 50 名上的假阳性/真阳性率。
- **[CHANGELOG.md](CHANGELOG.md)** — 完整的版本历史(v1.1 → v2.4)。
- **[SECURITY.md](SECURITY.md)** — 漏洞披露政策。
## 功能概述
`shai_hulud_guard` 可检测、移除并帮助预防 **Shai-Hulud npm/PyPI 供应链蠕虫** —— 这是一种自复制的蠕虫家族,在五个记录在案的波次中(2025 年 9 月至今)攻击了 npm 和 PyPI 生态系统,归因于威胁行为者 **TeamPCP**(DeadCatx3、PCPcat、ShellForce、CipherForce)。
该工具涵盖整个生命周期:
```
PREVENT DETECT RESPOND HARDEN
─────── ────── ─────── ──────
--check → --scan → --patch → --protect
--check-pypi --lockcheck --verify --unprotect
--self-test --diagnose
```
每个阶段都是一个独立的 flag —— 没有隐藏状态,没有后台 daemon,也没有交互式提示(除非你通过 `--protect` 要求)。该工具完全支持脚本化,对 CI 友好。
## 威胁背景
### 攻击时间线
| 波次 | 日期 | 入口向量 | 规模 |
|---|---|---|---|
| Wave 1 | 2025 年 9 月 | 被破坏的维护者凭据 | 500+ 个包 |
| Wave 2 | 2025 年 11 月 | CI/CD pipeline 注入 | 796 个包,1,092 个版本 |
| Wave 3 | 2026 年 3 月 | Aqua Security Trivy 包 | 定向攻击 |
| Wave 4 | 2026 年 4 月 | SAP npm 包;Bitwarden CLI | 定向攻击 |
| Wave 5 (Mini) | 2026 年 5 月 | GitHub Actions 缓存中毒 + OIDC 提取 | 172 个包,403 个版本,累计下载量 5.18 亿次 |
Wave 5 引入了**针对恶意包的有效 SLSA Build Level 3 来源证明** —— 绕过了所有标准验证工具。有关逐行攻击链,请参阅 [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md)。
### 攻击链(一段话摘要)
Fork → 通过 `pull_request_target` 毒化 Actions 缓存 → 合法 workflow 恢复中毒缓存 → 从 CI runner 内存中提取 OIDC token → `preinstall` hook 静默安装 Bun → `router_init.js`(约 2.3 MB 混淆代码)扫描 **npm token、GitHub PAT、AWS/GCP/Azure 密钥、SSH 密钥、`.gitconfig`、`.npmrc`** → 通过三个并行通道(`git-tanstack[.]com`、Session 网络、GitHub API dead drops)进行数据窃取 → 使用被盗的 npm token 发布受害者多达 100 个包的受感染版本 → 自我传播 → 安装 `gh-token-monitor` daemon,该 daemon 每 60 秒轮询一次 GitHub,如果任何 token 被吊销,则触发 `rm -rf ~/`。
最后一步就是为什么**在移除 daemon 之前吊销 token 是灾难性的**。`--patch` 和 `--incident` 都会强制执行正确的顺序。
### 为什么标准防御会失败(Wave 5)
| 防御手段 | 失败模式 |
|---|---|
| SLSA BL3 来源 | 在生成来源之前注入 —— 证明是有效的 |
| 维护者账号上的 2FA | 从 CI runner 内存中提取的 OIDC token,而不是从账号中获取 |
| `npm audit` | 为 CVE 设计,而非针对恶意代码注入 |
| 父级上的 `--ignore-scripts` | Git / file transitive deps 仍然会运行 `prepare` hooks |
| 信任知名包 | TanStack、Mistral AI、UiPath、OpenSearch 全部被攻破 |
## 安装说明
无需安装。单文件,仅依赖标准库 runtime。
```
# Clone
git clone https://github.com/USER/shai-hulud-guard.git
cd shai-hulud-guard
# 验证完整性
python shai_hulud_guard.py --version # → shai_hulud_guard 2.4.0
python shai_hulud_guard.py --self-test # → 6/6 PASSED
```
可选 —— 通过 PyInstaller 构建单文件二进制程序:
```
pip install -e ".[dev]"
python build.py # → dist/shai_hulud_guard[.exe]
```
规范的产物是 `.py` 文件。PyInstaller 二进制程序是为了方便没有安装 Python 的用户使用。
## 运行说明
每种模式都通过 `shai_hulud_guard.py` 上的一个 flag 调用。没有交互式菜单,没有隐藏状态。有关实时的 argparse 输出,请参见 `python shai_hulud_guard.py --help`。
### 预安装保护 —— 在你执行 `npm install` 或 `pip install` 之前
```
# npm
python shai_hulud_guard.py --check lodash # latest
python shai_hulud_guard.py --check @tanstack/react-router@1.169.5 # specific version
# PyPI
python shai_hulud_guard.py --check-pypi numpy
python shai_hulud_guard.py --check-pypi requests==2.31.0
```
返回 0–100 的风险评分。**当评分 ≥ 40 时退出代码为 1** —— `--protect` 生成的包装脚本使用此机制来阻止安装。
### 检测现有感染
```
python shai_hulud_guard.py --scan --path . # 8 checks
python shai_hulud_guard.py --lockcheck --path . # deep lockfile audit
```
### 响应已确认的感染
```
python shai_hulud_guard.py --patch --path . # classify + generate remediation scripts
python shai_hulud_guard.py --patch --path . --auto # also auto-run non-destructive steps
python shai_hulud_guard.py --verify --path . # re-scan after patch
python shai_hulud_guard.py --incident # printed 8-step recovery guide
```
### 加固以防范下一次攻击
```
python shai_hulud_guard.py --protect --path . # Phase 1 only (write inert files)
python shai_hulud_guard.py --protect --path . --setup-alias --setup-npmrc --setup-cron # also Phase 2 (modify system)
python shai_hulud_guard.py --unprotect --path . # full reversal
```
Phase 2 的每一项修改都包装在 sentinel 注释(`# === shai-hulud-guard … # === /shai-hulud-guard ===`)中 —— `--unprotect` 仅移除这些块,永远不会触及预先存在的用户内容。
### 机器可读输出
```
python shai_hulud_guard.py --json --scan --path .
python shai_hulud_guard.py --json --check intercom-client@7.0.4
```
stdout 上的单一 JSON 对象,没有 banner,没有 ANSI。schema 见 [docs/JSON_SCHEMA.md](docs/JSON_SCHEMA.md)。设计用于:
- 通过管道传递给 `jq` 以进行 CI 断言,
- 由下游工具使用而无需解析人类文本,
- 当你无法识别某个发现时,将其粘贴到前沿的 LLM 中以获取取证建议。
## 10 种模式 —— 每种模式的作用
### `--scan` —— 现有项目审计(8 项检查)
| # | 检查项 | 查找目标 |
|---|---|---|
| 1 | 持久化 daemon | 位于已记录的 Linux/macOS 路径下的 `gh-token-monitor`;Windows:通过已知的 daemon 名称查询 Task Scheduler + 启动文件夹 |
| 2 | `package.json` 审计 | 已知的恶意版本、高价值目标包、非 registry 依赖(`git:`、`github:`、`file:`),它们会无条件运行 `prepare` hooks |
| 3 | Lock file + `.npmrc` 卫生状况 | 缺失 lock file,npm 配置问题 |
| 3.5 | Lock file 深度分析 | 非 registry 的 `resolved` URL,缺失 `integrity` 哈希 |
| 4 | `node_modules` 深度扫描 | 已知的 payload 文件名 + 针对每个生命周期脚本(`preinstall`、`install`、`postinstall`、`prepare`)的 60+ IOC 模式 |
| 5 | 凭据文件清单 | 仅列出其存在性 —— **从不读取内容** |
| 6 | GitHub Actions workflows | `pull_request_target` + 缓存(Wave 5 向量),workflow 级别的 `id-token: write`,tag 固定的 actions |
| 7 | npm registry 配置 | 检测非默认 registry(潜在的 C2 重定向) |
| 8 | 已安装的 PyPI 包 | 将 `pip list` 与 `KNOWN_BAD` PyPI 条目进行交叉引用 |
### `--check` / `--check-pypi` —— 预安装风险分析(5 个步骤 + 2.5 个启发式方法)
```
STEP 1 Registry metadata (publish age, maintainers, version count)
STEP 2 Known compromised version DB (hard-block if confirmed bad)
STEP 2.5 Dynamic heuristics (maintainer drift, semver gap, new deps, typosquatting)
STEP 3 Lifecycle scripts (registry metadata, no execution)
STEP 4 Dependency source validation (flag git: / file: / http: deps)
STEP 5 Tarball download + integrity (SHA-512 verify, in-memory pattern scan)
```
PyPI 模式(`--check-pypi`)增加了 `.whl` wheel 扫描(zip 格式)和 SHA-256 完整性检查。注释剥离(`_strip_comments`)在源文件进行模式匹配之前运行,大幅减少了对合法包的误报(numpy:9/100,cryptography:0/100,react:0/100)。
### `--lockcheck` —— 专门的 lockfile 审计
规范化 `package-lock.json` v1(嵌套 `dependencies`)和 v2/v3(`packages` 字典)。标记:
- 非 registry 的 `resolved` URL。
- 缺失 `integrity` 哈希。
- 嵌入在 lockfile 条目中的生命周期脚本。
- 解析到 lock 中的已知恶意版本。
### `--patch` —— 感染分类器 + 修复脚本生成
对感染状态进行分类,然后编写特定于平台的修复脚本:
| 案例 | 触发条件 | 生成的脚本 |
|---|---|---|
| `CLEAN` | 无指标 | 无 —— 无需处理 |
| `UNCERTAIN` | 少数低置信度信号 | 无 —— 人工审查 |
| `LOW_CONFIDENCE` | 3+ 模式命中,无 daemon | 无 —— 先进行调查 |
| `DAEMON_ONLY` | 发现持久化,无恶意包 | `remove_daemon.{sh,ps1}` |
| `PACKAGES_ONLY` | 恶意包,无 daemon | `clean_packages.{sh,ps1}` |
| `FULL_COMPROMISE` | 两者都有 | 两者都有(先移除 daemon —— 在移除 daemon 之前绝不轮换 token) |
| `LOCKFILE_TAMPERED` | 关键的 lockfile 问题 | `clean_packages.{sh,ps1}` |
使用 `--auto` 时,会自动运行非破坏性步骤。**Token 轮换从不自动化** —— 请参阅 `docs/DESIGN.md § safety invariants`。
### `--verify` —— 补丁后重新扫描
在 `--patch` 之后重新运行 `--scan`。被生成的修复脚本用作其最后一步。
### `--self-test` —— 合成感染往返测试
在 `tempfile.TemporaryDirectory()` 中创建合成感染产物,运行扫描器,断言 6 个检测不变量,并进行清理。沙盒执行;从不执行任何代码。CI 使用它来检测扫描器本身的回归。
### `--diagnose` —— 用于事件交接的取证报告 *(Phase 3 — 即将推出)*
重新运行 `--scan` 并写入 `shai_hulud_report_.txt`,其中包含系统信息(OS、Python、CPU、hostname、user、CI 环境、shell、时间戳 —— 绝非凭据值)、发现列表以及可直接用于 LLM 的摘要。粘贴到 Claude / GPT-4 / Gemini 中,以获取分析师级别的事件处理指导。
### `--protect` / `--unprotect` —— 主动防御(Phase 1 + Phase 2)
**Phase 1 —— 始终安全的文件写入:**
- `npm_safe.sh` / `npm_safe.ps1` —— 在每次 `npm install` 之前运行 `--check` 的包装脚本
- `pip_safe.sh` / `pip_safe.ps1` —— 对 `pip install` 做同样处理的脚本
- `.github/workflows/shai_hulud_supply_chain.yml` —— SHA 固定的 CI workflow 模板
- `shai_hulud_pre_commit.hook` —— pre-commit hook 模板
**Phase 2 —— 可选的系统修改(交互式或通过 flags):**
- `--setup-alias` —— 在你的 profile 中为 `npm` 和 `pip` 设置 shell alias
- `--setup-npmrc` —— 在项目 `.npmrc` 中设置 `save-exact=true`
- `--setup-cron` —— 通过 cron(Linux/macOS)或 Task Scheduler(Windows)进行每日扫描
- `--install-hook` —— 将 pre-commit hook 安装到 `.git/hooks/` 中
Phase 2 的每一项修改都由 sentinel 包装。`--unprotect` 仅移除带括号的块,预先存在的用户内容不变。已由 `--self-test` 和 `tests/test_sentinel.py` 验证。
### `--incident` —— 打印的 8 步恢复指南
```
STEP 1 — STOP: Do NOT revoke tokens yet (daemon triggers rm -rf ~/)
STEP 2 — ISOLATE: Disconnect from network
STEP 3 — IMAGE: Forensic snapshot before cleanup
STEP 4 — REMOVE: Delete daemon (manually or via --patch generated scripts)
STEP 5 — ROTATE: NOW revoke: GitHub, npm, AWS/GCP/Azure, SSH
STEP 6 — AUDIT: Check npm publish history for unauthorised releases
STEP 7 — REBUILD: Wipe OS, rebuild from clean image
STEP 8 — REPORT: npm security@npmjs.com | CISA cisa.gov/reporting
```
**顺序是承重的 —— 请参阅 `docs/DESIGN.md`。**
## IOC 特征覆盖范围
### Payload 文件名(确凿的指标)
| 文件名 | 波次 |
|---|---|
| `router_init.js` | 所有波次 —— 核心 payload(约 2.3 MB) |
| `setup_bun.js` | Wave 2–5 —— Bun 安装程序存根 |
| `bun_environment.js` | Wave 2 |
| `setup.mjs` | Wave 1 —— ESM 变体 |
### 模式类别(跨 4 个风险级别的 60+ 条目)
| 类别 | 风险 | 示例 |
|---|---|---|
| 蠕虫身份 | CRITICAL | `Shai-Hulud`、`TeamPCP`、`gh-token-monitor` 活动标签 |
| 破坏性 payload | CRITICAL | `rm -rf ~/`、`rm -rf $HOME`、Windows 主目录擦除 |
| C2 基础设施 | CRITICAL | `git-tanstack.com`、`webhook.site/` |
| Token 字面量 | CRITICAL | `ghp_<36>`、`gho_<36>`、`npm_<36>` |
| CI 内存提取 | CRITICAL | `/proc//mem`、HTTP 上下文中的 OIDC 环境变量 |
| 持久化 | CRITICAL | LaunchAgent 路径、systemd 用户服务路径 |
| 云凭据 | HIGH | GCP ADC 路径、HTTP 上下文中的 `AWS_SECRET_ACCESS_KEY`、Azure 密钥 |
| Bun 注入 | HIGH | 生命周期脚本中的 `bun.sh/install` |
| GitHub API 滥用 | HIGH | 脚本中的 `api.github.com/user/repos` |
| 混淆 | HIGH | `eval(atob(...))`、≥ 40 个字符的 base64 字面量、作为 `\u00XX` 转义的 ASCII 字符 |
| Typosquatting | HIGH/MED | 与前 80 个 npm 包的 Levenshtein 距离 ≤ 2 |
| CI/CD 配置错误 | LOW | `pull_request_target` 仅在 CHECK 6 上下文中(带缓存)被标记 |
### 已知的受感染包(截至 v2.4 最新)
| 包名 | 确认的恶意版本 | 波次 |
|---|---|---|
| `@tanstack/react-router` | `1.169.5` | Wave 5 — 2026 年 5 月 |
| `@tanstack/router` | `1.169.5` | Wave 5 — 2026 年 5 月 |
| `@tanstack/react-query` | (审查标志) | Wave 5 — 2026 年 5 月 |
| `@mistralai/mistralai` | (审查标志) | Wave 5 — 2026 年 5 月 |
| `@uipath/apollo-core` | (审查标志) | Wave 5 — 2026 年 5 月 |
| `guardrails-ai` (PyPI) | `0.10.1` | Wave 5 — 2026 年 5 月 |
| `mistralai` (PyPI) | `2.4.6` | Wave 5 — 2026 年 5 月 |
| `intercom-client` | `7.0.4` | Wave 5 — 2026 年 5 月 |
| `@bitwarden/cli` | (审查标志) | Wave 4 — 2026 年 4 月 |
| `@ctrl/tinycolor` | (审查标志) | Wave 1 — 2025 年 9 月 |
| `@asyncapi/cli` | (审查标志) | Wave 2 — 2025 年 11 月 |
权威来源(按优先级排序):GitHub Advisory Database → NIST NVD → OSV → Datadog IOC repo。参见 `CLAUDE.md § 4.7`。
## 校准基准
每次模式更改都必须保留这些分数。运行 `python benchmarks/run_calibration.py` 针对实时 registry 进行验证。
| 包名 | 预期分数 | 备注 |
|---|:-:|---|
| `lodash` | 0/100 | 干净 |
| `react` | 0/100 | 干净 |
| `django` (PyPI) | 0/100 | 干净 |
| `flask` (PyPI) | 0/100 | 干净 |
| `cryptography` (PyPI) | 0/100 | 干净 —— 合法的 SSH/加密代码 |
| `numpy` (PyPI) | ≤ 25/100 | `numpy/distutils/command/egg_info.py` 中的一个 MEDIUM(合法的 `cmdclass={}`) |
| `boto3` (PyPI) | ≤ 40/100 | 干净的代码;分数偏高是因为刚发布的窗口期(boto3 每周发布多次) |
| `@tanstack/react-router` | ≤ 20/100 | 已知目标警告 + 审查标志 |
| `intercom-client@7.0.4` | **确认恶意** | 风险 = 100 时严格阻止 |
## 安全安装 workflow(当 `--check` 返回中等风险时)
```
# 1 — 安装且不执行 lifecycle scripts
npm install @ --ignore-scripts
# 2 — 检查已声明的 scripts
cat node_modules//package.json | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(json.dumps(d.get('scripts',{}),indent=2))"
# 3 — 搜索 payload 文件名
find node_modules/ -name "router_init.js" -o -name "setup_bun.js" -o -name "bun_environment.js"
# 4 — 仅在手动审查后重新启用 postinstall
# npm rebuild @
```
## 诚实的局限性
五个缺陷 —— 没有任何淡化。有关完整讨论,请参见 `docs/DESIGN.md § limitations`。
1. **模式规避** —— 每一次 Shai-Hulud 波次都引入了新的混淆。Zero-day 变种产生零模式发现。这不是理论上的担忧;它已经发生过。
2. **运行时获取的 payload** —— 在安装后运行时下载并执行代码的包不会被 tarball 扫描捕获。生命周期脚本引擎通过标记出站 fetch 模式来部分缓解此问题,但在扫描时无法阻止调用 `curl` 的干净 tarball。
3. **已知恶意列表延迟** —— 活跃攻击窗口(2-8 小时)早于公开披露。在此窗口期间,该工具会检测异常(发布时间、tarball 大小、可疑模式、维护者漂移),但无法从数据库确认是否受损。
4. **来源绕过** —— Wave 5 确认,当 build pipeline 本身受到破坏时,有效的 SLSA BL3 证明并不代表代码是干净的。该工具不验证来源,即使验证了也不会更有用。
5. **`--ignore-scripts` 绕过** —— git/file 来源的 transitive dependencies 会无条件运行 `prepare` hooks。扫描器在 CHECK 2 和预安装 STEP 4 中标记了这些,但根本的缓解措施需要审计完整的依赖树。
非零的风险评分是进行调查的强烈信号。**零风险评分并不能保证安全。**
## 贡献
IOC 更新和确认的新受感染版本是最高价值的贡献。
**要添加一个新的已知受感染版本**,请更新脚本中的 `KNOWN_BAD`:
```
"@example/package": {"bad": ["1.2.3"], "waves": ["Wave6-YYYY"], "advisories": ["GHSA-..."]},
```
**要添加模式**,请在 `MALICIOUS_PATTERNS` 中添加一个元组:
```
(r"your_regex", "Human-readable description", "CRITICAL|HIGH|MEDIUM|LOW"),
```
然后向 `tests/test_patterns.py::EXEMPLARS` 添加一个样本(测试套件拒绝没有样本的模式)。
**任何 IOC PR 的要求:**
- 新条目上方注释中的来源 URL。仅限权威来源 —— 参见 `CLAUDE.md § 4.7`。
- `--self-test` 必须仍然通过 6/6。
- 校准基准(如上)必须仍然成立。
- `python -m ruff check . && python -m pytest -q` 必须通过(绿色)。
未签名的添加将不会被合并 —— 这保持了签名集的可审计性,并防止攻击者通过 PR 进行 IOC 投毒。
## 架构(一句话)
单一 Python 文件。仅依赖标准库。对每次系统修改进行 sentinel 包装的可逆操作。针对命名的包校准模式。测试套件验证不变量。有关完整分解,请参见 `CLAUDE.md`。
## 参考
- **CISA** — `https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem`
- **GitHub Advisory Database** — `https://github.com/advisories`
- **NIST NVD** — `https://nvd.nist.gov/`
- **OSV** — `https://osv.dev/`
- **Datadog IOC repo** — `https://github.com/DataDog/indicators-of-compromise/tree/main/shai-hulud-2.0`
- **Datadog Security Labs** — `https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/`
- **Wiz** — `https://www.wiz.io/blog/mini-shai-hulud-strikes-again-tanstack-more-npm-packages-compromised`
- **StepSecurity** — `https://www.stepsecurity.io/blog/ctrl-tinycolor-and-40-npm-packages-compromised`
- **Snyk** — `https://snyk.io/blog/tanstack-npm-packages-compromised/`
- **Palo Alto Unit 42** — `https://unit42.paloaltonetworks.com/npm-supply-chain-attack/`
- **OX Security** — `https://www.ox.security/blog/shai-hulud-here-we-go-again-170-packages-hit-across-npm-pypi/`
## 许可证
[GPL-3.0-or-later](LICENSE)。有关披露政策,请参见 [SECURITY.md](SECURITY.md)。
这是一个**防御性安全工具**。源代码包含可能被静态分析工具和 AV 引擎标记的模式签名和合成感染产物(由 `--self-test` 使用)。在此上下文中,这些标记属于误报 —— 请参见 `SECURITY.md § A note on the project's content`。
标签:IP 地址批量处理, Python-CLI, 域名收集, 恶意软件查杀, 文档结构分析, 无线安全, 网络信息收集, 跌倒检测, 软件物料清单, 逆向工具