bootproof/repo-proofer
GitHub: bootproof/repo-proofer
在零网络沙箱中实际运行可疑 Git 仓库,通过 strace 检测密钥外泄行为并验证 README 声明真实性的确定性安全筛查工具。
Stars: 0 | Forks: 0
# repo-proofer
` —— 无需克隆,无需 venv,无需设置。已发布在 [PyPI](https://pypi.org/project/repo-proofer/) 上。
## 安装说明
使用 `uvx` 立即运行 `repo-proofer` —— 无需克隆,无需 venv,无需设置:
```
uvx repo-proofer https://github.com/owner/repo.git
```
这就行了。`uvx` 会创建一个临时的隔离环境,安装依赖项,克隆目标 repo,启动沙箱,运行 strace,打印判定结果,并在完成后自动清理。
其他安装方法:
```
# 使用 pipx 永久安装:
pipx install repo-proofer
repo-proofer https://github.com/owner/repo.git
# 或使用 pip:
pip install repo-proofer
# 或从源码安装(用于开发):
git clone https://github.com/bootproof/repo-proofer.git
cd repo-proofer
pip install -e .
```
需要 Python 3.10+。
**在 Linux 上(零配置,即时启动):** 安装 `bubblewrap` 和 `strace` —— 两者都是原生沙箱和数据外泄检测所必需的:
```
sudo apt install bubblewrap strace # Debian/Ubuntu
sudo dnf install bubblewrap strace # Fedora
```
然后 `uvx repo-proofer ` 将在约 1.5 秒内运行,无需 Docker 守护进程,无需拉取镜像。这是推荐的方式。
**在 macOS / Windows 上:** 原生沙箱不可用(bubblewrap 仅限 Linux 使用)。`--sandbox auto` 会回退到 Docker —— 在运行 Docker Desktop 的情况下执行 `repo-proofer `。首次运行会拉取镜像(约数分钟);后续运行速度很快。
## 功能
### 对 repo 进行分类鉴定
将 `repo-proofer` 指向任何 Git URL。它会克隆、检测技术栈、安装依赖、在锁定的沙箱中执行 entrypoint,并输出判定结果。
```
$ uvx repo-proofer https://github.com/pallets/markupsafe.git
╭─ repo-proofer verdict ──────────────────────────────╮
│ Detected Stack Python │
│ BOOTS NO RUNNABLE ENTRYPOINT │
│ Detail no runnable entrypoint │
│ (looks like a library) │
│ Network Egress BLOCKED │
│ Filesystem READ-ONLY │
╰──────────────────────────────────────────────────────╯
```
`markupsafe` 是一个库 —— 没有 `main.py`,无可运行内容。黄色的判定是正确的:它不是垃圾,只是没有 entrypoint。CI 会退出并返回代码 0。
### 捕捉恶意 repo
这个 `slop-repo` 测试夹具会伪装成一家 AI 初创公司,同时暗中读取 `~/.ssh/id_rsa`,然后连接到 C2 服务器。在 `repo-proofer` 的 `--network none` 沙箱下,对外连接失败,strace 会捕捉到读取机密的行为:
```
$ uvx repo-proofer file://$(pwd)/tests/fixtures/slop-repo
╭─ repo-proofer verdict ──────────────────────────────╮
│ BOOTS NO │
│ Detail exited 1 (crash) │
│ Warnings [!] App crashed when network was │
│ blocked. │
╰──────────────────────────────────────────────────────╯
╭─ Sensitive File Access — HIGH (2) ───────────────────╮
│ - /root/.ssh/id_ed25519 │
│ - /root/.ssh/id_rsa │
╰──────────────────────────────────────────────────────╯
[!] EXFILTRATION DETECTED — high-risk sensitive file access
correlated with network attempt(s). Secret paths:
/root/.ssh/id_ed25519, /root/.ssh/id_rsa.
Primary indicator of malicious intent.
```
退出代码为 1。“malicious intent”(恶意意图)的字眼是**实至名归的** —— 只有当 HIGH(高危)级别的机密读取(SSH 密钥、`.env`、AWS 凭证)与网络尝试相关联时,才会触发该提示。如果一个 repo 读取了 `.npmrc` 但没有进行任何网络调用,它的状态将保持为黄色,而不是红色。绝不产生误报。
### 验证 README 声明
`repo-proofer` 会读取 README,提取可测试的声明,并将每一项映射到运行时证据:
```
╭─ README Claim Verification ──────────────────────────╮
│ Claims Verified 3 of 3 testable │
│ │
│ All 3 testable README claims verified by execution. │
╰──────────────────────────────────────────────────────╯
╭─ Verified (3) ───────────────────────────────────────╮
│ ✓ Server starts on port 3000 │
│ App bound to port 3000 (strace bind() observed) │
│ ✓ pip install -r requirements.txt │
│ Install used: pip install -r requirements.txt │
│ ✓ Built with Flask │
│ Framework in requirements.txt │
╰──────────────────────────────────────────────────────╯
```
如果一个 repo 能正常启动,但其 5 项声明中通过验证的数量为 0,它就会被标记为**极有可能是垃圾** —— 它的 README 承诺了代码并未实现的功能。
### 抓出流行词垃圾
12 个正则表达式模式用于检测无法通过执行来验证的营销话术:
```
╭─ Buzzword Claims (11) ───────────────────────────────╮
│ Marketing terms — cannot be verified by execution. │
│ High concentration of these is a slop signal. │
│ │
│ ~ Quantum-Enhanced GPT-5 + Blockchain Audit Trail │
│ ~ AI-Powered code review │
│ ~ Predictive Auto-Scaling │
│ ~ Zero-Trust Security │
│ ~ Carbon-Aware │
│ ~ Self-Healing │
│ ~ Edge-Native Architecture │
│ ... (4 more) │
╰──────────────────────────────────────────────────────╯
```
包含 11 个流行词和 2 个可测试声明的 README 会被标记为垃圾 —— 即使那 2 个可测试声明通过了验证也是如此。流行词计数就是一眼就能看出的垃圾信号。
### 沙箱机制
两种后端,同样坚不可摧的防御:
```
$ repo-proofer --sandbox native # bubblewrap (Linux, no Docker, milliseconds)
$ repo-proofer --sandbox docker # Docker (clean-room images, memory/CPU limits)
$ repo-proofer --sandbox auto # default: prefer native, fall back to Docker
```
两种后端都强制执行相同的安全约束:
| 约束 | Docker 模式 | 原生模式 |
|---|---|---|
| 网络 | `--network none` | `--unshare-net` |
| 文件系统 | `--read-only` + `--tmpfs /tmp` | `--ro-bind /usr` + `--tmpfs /tmp` |
| SSH 密钥 | 未挂载 | `--tmpfs /home` + `--tmpfs /root` (空) |
| Capabilities | `--cap-drop ALL` | bubblewrap 默认丢弃所有 |
| Repo | `-v repo:/app:ro` | `--ro-bind repo /app` |
如果应用因为无法连接网络而崩溃,**那说明成功检测到了隐藏的依赖项,而不是工具本身运行失败。**
### 技术栈检测
`repo-proofer` 通过标记文件检测技术栈,并解析入口点:
| 标记 | 技术栈 | Entrypoint 解析规则 |
|---|---|---|
| `package.json` | Node.js | `scripts.start`, `main`, `bin`, 其次是 `index.js`/`app.js`/`server.js` |
| `requirements.txt` / `pyproject.toml` / `setup.py` / `setup.cfg` | Python | `[project.scripts]`, `console_scripts`, `main.py`/`app.py`/`server.py`/`run.py`, `manage.py check`, `src/` 布局, `python -m ` |
| `Gemfile` + `config.ru` | Ruby (Rails) | `bundle exec rails server` |
| `go.mod` | Go (实验性) | `go run main.go` |
| `Cargo.toml` | Rust (实验性) | `cargo run --offline` |
多语言混合 repo(如 Rails + 前端 `package.json`,Django + webpack)能正确解析为其主要应用语言 —— 次要的 `package.json` 不会掩盖 Rails 或 Django 应用。
### 退出代码
```
$ repo-proofer ; echo "exit: $?"
exit: 0 # boots cleanly, OR is a library (yellow)
exit: 1 # crashed, or attempted sensitive file access (red)
exit: 2 # clone failed
exit: 3 # sandbox unavailable (no Docker / no bubblewrap)
exit: 4 # could not detect project stack
exit: 5 # failed to pull Docker image
```
该退出代码对 CI 非常友好:将其接入 GitHub Actions 工作流中,任何崩溃或触碰机密的 repo 都将阻断 PR 的合并。
## 工作原理
```
1. Clone git clone --depth=1 (network ON)
2. Detect filesystem checks for marker files (deterministic)
3. Install sandbox ... (network ON, 60s)
4. Execute sandbox --network=none --read-only ... (network OFF)
└─ strace -ff -e trace=openat,connect,bind (behavior report)
5. Analyze regex on stdout/stderr + strace trace (deterministic)
6. Claims extract README claims → match to evidence (deterministic)
7. Verdict three-color panel + exit code
```
不使用 LLM。不调用 AI API。纯 subprocess + filesystem + strace 实现。基于 LLM 的分析器会更慢、成本更高,而且容易受到 repo 自身 README 中 prompt 注入的攻击。纯粹的确定性是其核心特性。
## 局限性
本工具对其能做和不能做的事情非常坦诚。
- **在 Docker 模式下,首次运行需要几分钟。** Docker 后端需要拉取基础镜像并构建 strace 镜像。原生后端(Linux 上的默认选项)无需拉取镜像 —— 它直接使用主机的 runtime,并在毫秒级内启动。
- **Go 和 Rust 支持处于实验阶段。** 两者都在 `--network none` 下运行,且没有安装步骤,因此带有外部依赖的项目无法在运行时获取它们。只有零依赖或预打包好的 Go/Rust 项目才能启动。
- **声明验证基于正则表达式,而非语义分析。** 我们使用正则表达式模式(而非 LLM)来提取可测试的断言(端口、服务、框架、安装命令、文件类型)。这意味着我们会遗漏一些细微复杂的声明,但我们提取的每一项声明都是可检查的,且提取过程可复现。流行词检测则用于抓出那些无法验证的营销词汇。
- **基于主机名的 C2 检测是间接的。** 在 `--network none` 环境下,DNS 解析会在 `connect()` 之前失败,因此基于主机名的外发目标会显示为对 DNS 解析器的 DNS 查询,而不是实际的主机名。不过,无论如何,**敏感文件访问**列表都是强有力且明确的信号。
- **安装阶段的残留风险。** 安装阶段是在开启网络的情况下运行的(必须如此才能获取软件包)。npm 的供应链风险窗口通过 `--ignore-scripts` 被关闭;pip 在 `--prefer-binary` 的引导下倾向于使用 wheels。仅包含 sdist 的包仍会触发 PEP 517 构建 —— 这是一个已知的残留风险。
- **原生沙箱仅限 Linux。** macOS/Windows 上不存在 Bubblewrap。在这些平台上,`--sandbox auto` 会回退到 Docker。此外,原生沙箱没有内存/CPU 限制 —— 若需完整的隔离配置,请使用 `--sandbox docker`。
- **速度与隔离性的权衡。** 默认的 `--sandbox auto` 更倾向于使用原生 bubblewrap 沙箱(快速,无需 Docker),而不是 Docker(无尘室隔离,cgroup 限制)。如果只是想知道“这是不是垃圾 / 它会不会对外发送请求”,原生模式是一个合理的折中选择。如果怀疑是“这可能是指向我的定向恶意软件”,请使用 `--sandbox docker` 获取完整的容器隔离。
## 常见问题解答
#### 为什么不直接自己读代码?
你可以——而且对于你信任的 repo,你也应该这么做。但在 95% 的常见场景中(“一个有着花哨 README 的陌生 repo”),阅读每一行 `setup.py` 和 `postinstall.sh` 所花的时间比运行 `repo-proofer` 还要长,而且代码混淆可以隐藏其真实意图,让人类读者无法察觉。`repo-proofer` 观察的是底层物理行为:如果应用试图打开 socket,内核就会告诉我们。你无法混淆 syscall。
#### 这与 Snyk / Socket / GitHub Advanced Security 有什么区别?
那些工具进行的是*静态分析* —— 它们阅读代码,看它是否看起来有恶意。`repo-proofer` 进行的是*动态执行* —— 它在一个锁定的箱子里运行代码,观察它实际做了什么。静态分析是可以被绕过的(通过代码混淆、由环境触发的 payload 等)。动态执行则不行:如果一个恶意 repo 需要对外连接以下载其 payload,在 `--network none` 环境下它物理上根本做不到。
#### 对于永不退出的服务器,“BOOTS: YES”意味着什么?
一个时但未崩溃的进程就是一个健康的长时间运行进程(服务器、daemon、机器人)。其判定结果为 `BOOTS: YES (long-running)`。如果它还打印了就绪信号(如 `"listening on port 8080"`、`"Uvicorn running"`),判定结果将升级为 `BOOTS: YES (server detected)`,并显示匹配到的信号。
#### 声明验证是如何工作的?
`repo-proofer` 会读取 README,并应用 15 个正则表达式模式来提取可测试的断言:端口号、数据库服务、API 集成、安装命令、运行命令、文件类型和框架。然后将每项声明与 strace 跟踪记录和执行输出进行匹配。当 strace 显示在端口 3000 上执行了 `bind()` 时,“在端口 3000 上启动”的声明即为 VERIFIED(已验证)。无法检查的声明会被标记为 UNVERIFIABLE(无法验证)—— 绝不会默默忽略。
#### 流行词检测是如何工作的?
12 个正则表达式模式用于检测常见的 AI 垃圾营销词汇:“quantum-enhanced”、“blockchain-secured”、“AI-powered”、“zero-trust security”、“predictive auto-scaling”、“self-healing”、“carbon-aware”、“edge-native”、“5G-optimized” 等等。这些声明永远是 UNVERIFIABLE(无法验证)的 —— 它们只是没有任何可测试运行时行为的营销词汇。高流行词计数是一眼可见的垃圾信号。
#### 它能在 macOS 上运行吗?
可以,但需要 Docker。`--sandbox auto` 在 macOS 上会回退到 Docker(bubblewrap 仅限 Linux 使用)。`uvx repo-proofer ` 可以正常工作 —— 只需要运行 Docker Desktop 即可。
#### 它准备好投入生产环境了吗?
引擎非常稳定,确定性测试套件(91 个测试)在每次提交时都能通过。原生 bubblewrap 沙箱比 Docker 模式更新一些 —— 如果需要完整的隔离配置,请使用 `--sandbox docker`。有关当前状态,请参阅 [CI 徽章](https://github.com/bootproof/repo-proofer/actions)。
## 贡献指南
欢迎踊跃贡献。确定性核心逻辑请参阅[测试套件](scripts/smoke_test.py),Docker 集成测试请参阅 [tests/integration_test.py](tests/integration_test.py)。在提交 PR 之前,请运行 `python scripts/smoke_test.py` 进行验证。
## 开源许可
[MIT](LICENSE)
查明一个 repo 究竟会窃取你的密钥——还是会当面撒谎——在你运行它之前。
GitHub 上充斥着 AI 生成的“垃圾内容”——这些代码库拥有花哨的 README,但根本无法运行;或者更糟的是,在你执行 `npm install` 的瞬间,它们就会悄悄连接到 C2 服务器。`repo-proofer` 会克隆一个 repo,将其放入一个零网络、只读的沙箱中执行,并告诉你三件事——**纯确定性的,不依赖 AI**: 1. **它能启动吗?** —— 三色判定(绿/红/黄) 2. **它会窃取你的密钥吗?** —— 基于 strace 的数据外泄检测 3. **它的 README 说了实话吗?** —— 结合流行词检测的声明验证那个垃圾 repo 测试夹具被抓了个现行:窃取 SSH 密钥、进行网络传出,以及 11 个流行词谎言。
``` $ uvx repo-proofer file://$(pwd)/tests/fixtures/slop-repo ╭─ repo-proofer verdict ──────────────────────────────╮ │ BOOTS NO │ │ Detail exited 1 (crash) │ │ Network Egress BLOCKED │ │ Filesystem READ-ONLY │ │ Warnings [!] App crashed when network was │ │ blocked. │ ╰──────────────────────────────────────────────────────╯ ╭─ Sensitive File Access — HIGH (2) ───────────────────╮ │ - /root/.ssh/id_ed25519 │ │ - /root/.ssh/id_rsa │ ╰──────────────────────────────────────────────────────╯ ╭─ README Claim Verification ──────────────────────────╮ │ Claims Verified 2 of 2 testable │ │ Buzzword Claims 11 (not machine-verifiable) │ │ │ │ All 2 testable claims verified (11 buzzword claims │ │ not machine-verifiable) │ ╰──────────────────────────────────────────────────────╯ ╭─ Buzzword Claims (11) ───────────────────────────────╮ │ Marketing terms — cannot be verified by execution. │ │ High concentration of these is a slop signal. │ │ ~ Quantum-Enhanced GPT-5 + Blockchain Audit Trail │ │ ~ AI-Powered code review │ │ ~ Predictive Auto-Scaling │ │ ~ Zero-Trust Security │ │ ~ Carbon-Aware │ │ ~ Self-Healing │ │ ... (5 more) │ ╰──────────────────────────────────────────────────────╯ [!] EXFILTRATION DETECTED — high-risk sensitive file access correlated with network attempt(s). Secret paths: /root/.ssh/id_ed25519, /root/.ssh/id_rsa. Primary indicator of malicious intent. ``` 一条命令。三个答案。零 AI。 ## 核心亮点 - **安全地运行不受信任的代码。** 每次执行都在配置了 `--network none` 和 `--read-only` 的沙箱中进行。该 repo 无法对外发送请求,不能在 `/tmp` 之外写入数据,也无法读取 `~/.ssh`。 - **能发现静态分析发现不了的问题。** Snyk、Socket 和 GitHub Advanced Security 是通过阅读代码来判断它是否*看起来*有恶意。而 `repo-proofer` 会直接运行它,并观察它*实际做了什么*。代码混淆可以骗过 linter,但骗不过拒绝打开 socket 的内核。 - **检测数据外泄,而不仅仅是访问。** 只有当读取 `~/.ssh/id_rsa` 与网络请求(确凿的犯罪证据)相关联时,才会被标记为 `EXFILTRATION DETECTED`。如果读取配置文件时没有任何网络调用,则判定保持为黄色,而非红色。绝不产生误报。 - **根据执行情况验证 README 声明。** 从 README 中提取可测试的断言(端口、服务、框架、安装命令),并将每项映射到运行时证据。当 strace 显示在端口 3000 上执行了 `bind()` 时,“在端口 3000 上启动”这一声明状态即为 VERIFIED(已验证)。无法检查的声明会被标记为 UNVERIFIABLE(无法验证)——绝不会默默忽略。 - **识别流行词垃圾。** 12 个正则表达式模式用于检测无法通过执行验证的营销话术(“quantum-enhanced”、“blockchain-secured”、“AI-powered”、“zero-trust security”)。如果某个 README 包含 11 个流行词和 2 个可测试声明,即使那 2 个声明通过了验证,它也会被标记为垃圾。 - **100% 确定性,零 AI。** 纯 subprocess + filesystem + strace 实现。没有 LLM,没有 API 调用,没有 prompt 注入攻击面。每次运行结果相同,且永久免费。 - **在 Linux 上无需 Docker。** 默认的 `--sandbox auto` 使用原生的 bubblewrap 沙箱——毫秒级启动,无需拉取镜像。Docker 是 macOS/Windows 的备选方案,或者使用 `--sandbox docker` 获得全面的无尘室隔离。 - **三色判定结果。** 绿色 `BOOTS: YES`(它运行了),红色 `BOOTS: NO`(它崩溃了或试图窃取机密),黄色 `NO RUNNABLE ENTRYPOINT`(它是一个库,而不是垃圾)。库不会被判定为与恶意软件相同的红色。 - **一条命令即可安装。** `uvx repo-proofer标签:IP 地址批量处理, Python, StruQ, 数据窃取检测, 无后门, 沙箱, 网络信息收集, 请求拦截, 逆向工具