bootproof/repo-proofer

GitHub: bootproof/repo-proofer

在零网络沙箱中实际运行可疑 Git 仓库,通过 strace 检测密钥外泄行为并验证 README 声明真实性的确定性安全筛查工具。

Stars: 0 | Forks: 0

# repo-proofer

查明一个 repo 究竟会窃取你的密钥——还是会当面撒谎——在你运行它之前。

PyPI PyPI downloads Python versions CI repo-proofer License GitHub stars

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 ` —— 无需克隆,无需 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)
标签:IP 地址批量处理, Python, StruQ, 数据窃取检测, 无后门, 沙箱, 网络信息收集, 请求拦截, 逆向工具