repo-proofer 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 bootproof
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,283 @@
1
+ Metadata-Version: 2.4
2
+ Name: repo-proofer
3
+ Version: 0.2.0
4
+ Summary: Brutally honest verdicts for AI-generated GitHub repos. 100% deterministic. No LLMs.
5
+ Author: bootproof
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/bootproof/repo-proofer
8
+ Project-URL: Repository, https://github.com/bootproof/repo-proofer
9
+ Project-URL: Issues, https://github.com/bootproof/repo-proofer/issues
10
+ Keywords: security,supply-chain,sandbox,docker,strace,sbom,ai-slop,deterministic
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Security
21
+ Classifier: Topic :: Software Development :: Quality Assurance
22
+ Classifier: Topic :: System :: Systems Administration
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: typer>=0.12.0
27
+ Requires-Dist: rich>=13.7.0
28
+ Requires-Dist: GitPython>=3.1.40
29
+ Dynamic: license-file
30
+
31
+ # repo-proofer
32
+
33
+ [![CI](https://github.com/bootproof/repo-proofer/actions/workflows/ci.yml/badge.svg)](https://github.com/bootproof/repo-proofer/actions)
34
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
35
+ [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
36
+ [![PyPI](https://img.shields.io/pypi/v/repo-proofer.svg)](https://pypi.org/project/repo-proofer/)
37
+
38
+ Find out if a repo will steal your keys before you run it.
39
+
40
+ GitHub is flooded with AI-generated "slop" — repositories with impressive READMEs that don't actually run, or worse, quietly phone home to a C2 server the moment you `npm install` them. `repo-proofer` clones a repo, drops it into a zero-network, read-only sandbox, executes it, and tells you — deterministically, no AI — whether it booted and whether it tried to read your SSH keys.
41
+
42
+ <p align="center">
43
+ <i>The slop-repo fixture being caught red-handed.</i>
44
+ </p>
45
+
46
+ ```
47
+ $ repo-proofer file://$(pwd)/tests/fixtures/slop-repo
48
+
49
+ ╭─ repo-proofer verdict ──────────────────────────────╮
50
+ │ Repository file://.../slop-repo │
51
+ │ Detected Stack Python │
52
+ │ BOOTS NO │
53
+ │ Detail exited 1 (crash) │
54
+ │ Network Egress BLOCKED │
55
+ │ Filesystem READ-ONLY │
56
+ │ Warnings [!] App crashed when network was │
57
+ │ blocked. May require external API │
58
+ │ to function. │
59
+ ╰──────────────────────────────────────────────────────╯
60
+
61
+ ╭─ Sensitive File Access (3) ──────────────────────────╮
62
+ │ - /etc/passwd │
63
+ │ - /root/.ssh/id_ed25519 │
64
+ │ - /root/.ssh/id_rsa │
65
+ ╰──────────────────────────────────────────────────────╯
66
+
67
+ [!] Sensitive file access detected — primary indicator of malicious intent.
68
+ ```
69
+
70
+ ## Highlights
71
+
72
+ - **Runs untrusted code safely.** Every execution is sandboxed with the network disabled and the filesystem read-only. The repo can't phone home. It can't write outside `/tmp`. It can't read `~/.ssh`.
73
+ - **Catches what static analysis can't.** Snyk, Socket, and GitHub Advanced Security read code to see if it *looks* malicious. `repo-proofer` runs it and watches what it *does*. Obfuscation can fool a linter. It cannot fool a kernel that refuses to open a socket.
74
+ - **100% deterministic, zero AI.** Pure subprocess + filesystem + strace. No LLMs, no API calls, no prompt-injection surface. Same answer every time, free to run forever.
75
+ - **No Docker required.** The default `--sandbox auto` uses a native bubblewrap sandbox on Linux — millisecond startup, no image pulls. Docker is the fallback for macOS/Windows or `--sandbox docker` for full clean-room isolation.
76
+ - **Three-color verdicts.** Green `BOOTS: YES` (it ran), red `BOOTS: NO` (it crashed or tried to steal secrets), yellow `NO RUNNABLE ENTRYPOINT` (it's a library, not slop). Libraries don't get the same red as malware.
77
+ - **Runtime Behavior Report.** strace traces every syscall inside the sandbox. You get an SBOM-style report based on *actual execution*: files read, files written, processes spawned, network calls attempted, sensitive paths touched.
78
+ - **Installable in one command.** `uvx --from git+https://github.com/bootproof/repo-proofer.git repo-proofer <url>` — no clone, no venv, no setup. Works today, straight from GitHub.
79
+
80
+ ## Installation
81
+
82
+ Run `repo-proofer` instantly with `uvx` — no clone, no venv, no setup:
83
+
84
+ ```bash
85
+ uvx --from "git+https://github.com/bootproof/repo-proofer.git" repo-proofer https://github.com/owner/repo.git
86
+ ```
87
+
88
+ That's it. `uvx` creates an ephemeral isolated environment, installs `typer`/`rich`/`GitPython`, clones the target repo, spins up the sandbox, runs the strace, prints the verdict, and cleans up after itself.
89
+
90
+ Once published to PyPI, the command shortens to:
91
+
92
+ ```bash
93
+ uvx repo-proofer https://github.com/owner/repo.git
94
+ ```
95
+
96
+ Other install methods:
97
+
98
+ ```bash
99
+ # Install permanently with pipx (from GitHub):
100
+ pipx install "git+https://github.com/bootproof/repo-proofer.git"
101
+ repo-proofer https://github.com/owner/repo.git
102
+
103
+ # Or from source (for development):
104
+ git clone https://github.com/bootproof/repo-proofer.git
105
+ cd repo-proofer
106
+ pip install -e .
107
+ ```
108
+
109
+ Requires Python 3.10+. The native sandbox (default on Linux) needs `bubblewrap` — install it with `apt install bubblewrap` (Debian/Ubuntu) or `dnf install bubblewrap` (Fedora). No Docker needed.
110
+
111
+ ## Documentation
112
+
113
+ See the [Limitations](#limitations) section for honest gaps, and the [FAQ](#faq) for common questions. The command-line reference is available with `repo-proofer --help`.
114
+
115
+ ## Features
116
+
117
+ ### Triage a repo
118
+
119
+ Point `repo-proofer` at any Git URL. It clones, detects the stack, installs deps, executes the entrypoint in a locked sandbox, and prints a verdict.
120
+
121
+ ```console
122
+ $ repo-proofer https://github.com/pallets/markupsafe.git
123
+ Cloning https://github.com/pallets/markupsafe.git (depth=1)...
124
+ Detected stack: Python | Native sandbox (bubblewrap)
125
+ Installing dependencies (network ON, timeout 60s)...
126
+ Executing entrypoint (network OFF, read-only FS, timeout 30s)...
127
+
128
+ ╭─ repo-proofer verdict ──────────────────────────────╮
129
+ │ Repository https://github.com/pallets/mar… │
130
+ │ Detected Stack Python │
131
+ │ BOOTS NO RUNNABLE ENTRYPOINT │
132
+ │ Detail no runnable entrypoint │
133
+ │ (looks like a library) │
134
+ │ Network Egress BLOCKED │
135
+ │ Filesystem READ-ONLY │
136
+ ╰──────────────────────────────────────────────────────╯
137
+ ```
138
+
139
+ `markupsafe` is a library — no `main.py`, nothing to run. The yellow verdict is correct: it's not slop, it just has no entrypoint. CI exits 0.
140
+
141
+ ### Catch a malicious repo
142
+
143
+ The `slop-repo` fixture impersonates an AI startup while quietly reading `~/.ssh/id_rsa` and `/etc/passwd`, then phoning home to a C2 server. Under `repo-proofer`'s `--network none` sandbox, the phone-home fails and strace catches the secret reads:
144
+
145
+ ```console
146
+ $ repo-proofer file://$(pwd)/tests/fixtures/slop-repo
147
+
148
+ ╭─ repo-proofer verdict ──────────────────────────────╮
149
+ │ BOOTS NO │
150
+ │ Detail exited 1 (crash) │
151
+ │ Warnings [!] App crashed when network was │
152
+ │ blocked. │
153
+ ╰──────────────────────────────────────────────────────╯
154
+
155
+ ╭─ Runtime Behavior Report ────────────────────────────╮
156
+ │ Files Read 2 │
157
+ │ Network Calls Attempted 1 │
158
+ │ Sensitive File Access 3 (see below) │
159
+ ╰──────────────────────────────────────────────────────╯
160
+
161
+ ╭─ Network Calls Attempted (1) ────────────────────────╮
162
+ │ - connect 203.0.113.42:443 │
163
+ ╰──────────────────────────────────────────────────────╯
164
+
165
+ ╭─ Sensitive File Access (3) ──────────────────────────╮
166
+ │ - /etc/passwd │
167
+ │ - /root/.ssh/id_ed25519 │
168
+ │ - /root/.ssh/id_rsa │
169
+ ╰──────────────────────────────────────────────────────╯
170
+
171
+ [!] Sensitive file access detected — primary indicator of malicious intent.
172
+ ```
173
+
174
+ Exit code 1. The verdict is unambiguous: this repo tried to steal your keys.
175
+
176
+ ### The sandbox
177
+
178
+ Two backends, same moat:
179
+
180
+ ```console
181
+ $ repo-proofer <url> --sandbox native # bubblewrap (Linux, no Docker, milliseconds)
182
+ $ repo-proofer <url> --sandbox docker # Docker (clean-room images, memory/CPU limits)
183
+ $ repo-proofer <url> --sandbox auto # default: prefer native, fall back to Docker
184
+ ```
185
+
186
+ Both backends enforce the same security constraints:
187
+
188
+ | Constraint | Docker mode | Native mode |
189
+ |---|---|---|
190
+ | Network | `--network none` | `--unshare-net` |
191
+ | Filesystem | `--read-only` + `--tmpfs /tmp` | `--ro-bind /usr` + `--tmpfs /tmp` |
192
+ | SSH keys | not mounted | `--tmpfs /home` + `--tmpfs /root` (empty) |
193
+ | Capabilities | `--cap-drop ALL` | bubblewrap drops all by default |
194
+ | Repo | `-v repo:/app:ro` | `--ro-bind repo /app` |
195
+
196
+ If the app crashes because it can't reach the network, **that is a successful detection of a hidden dependency, not a tool failure.**
197
+
198
+ ### Stack detection
199
+
200
+ `repo-proofer` detects the stack from marker files and resolves the entrypoint:
201
+
202
+ ```console
203
+ $ repo-proofer https://github.com/owner/my-cli.git
204
+ Detected stack: Python | Native sandbox (bubblewrap)
205
+ Executing entrypoint (network OFF, read-only FS, timeout 30s)...
206
+ ```
207
+
208
+ | Marker | Stack | Entrypoint resolution |
209
+ |---|---|---|
210
+ | `package.json` | Node.js | `scripts.start`, `main`, `bin`, then `index.js`/`app.js`/`server.js` |
211
+ | `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/` layout, `python -m <pkg>` |
212
+ | `go.mod` | Go (experimental) | `go run main.go` |
213
+ | `Cargo.toml` | Rust (experimental) | `cargo run --offline` |
214
+
215
+ A modern CLI that declares its entrypoint only in `[project.scripts]` (no `main.py`) is correctly detected as runnable — not mislabeled as a library.
216
+
217
+ ### Exit codes
218
+
219
+ ```console
220
+ $ repo-proofer <url>; echo "exit: $?"
221
+ exit: 0 # boots cleanly, OR is a library (yellow)
222
+ exit: 1 # crashed, or attempted sensitive file access (red)
223
+ exit: 2 # clone failed
224
+ exit: 3 # sandbox unavailable (no Docker / no bubblewrap)
225
+ exit: 4 # could not detect project stack
226
+ exit: 5 # failed to pull Docker image
227
+ ```
228
+
229
+ The exit code is CI-friendly: wire it into a GitHub Actions workflow and any repo that crashes or touches secrets blocks the PR.
230
+
231
+ ## How it works
232
+
233
+ ```
234
+ 1. Clone git clone --depth=1 (network ON)
235
+ 2. Detect filesystem checks for marker files (deterministic)
236
+ 3. Install sandbox ... <install_cmd> (network ON, 60s)
237
+ 4. Execute sandbox --network=none --read-only ... (network OFF)
238
+ └─ strace -ff -e trace=openat,connect,... (behavior report)
239
+ 5. Analyze regex on stdout/stderr + strace trace (deterministic)
240
+ 6. Verdict three-color panel + exit code
241
+ ```
242
+
243
+ No LLMs. No AI APIs. Pure subprocess + filesystem + strace. An LLM-based analyzer would be slower, more expensive, and gameable via prompt injection in the repo's own README. Pure determinism is the core feature.
244
+
245
+ ## Limitations
246
+
247
+ This tool is honest about what it can and can't do.
248
+
249
+ - **First run is minutes in Docker mode.** The Docker backend pulls base images and builds a strace image. The native backend (default on Linux) has no image pulls — it uses the host's runtimes and starts in milliseconds.
250
+ - **Go and Rust are experimental.** Both run under `--network none` with no install step, so projects with external dependencies can't fetch them at runtime. Only zero-dependency or pre-vendored Go/Rust projects boot.
251
+ - **Hostname-based C2 detection is indirect.** Under `--network none`, DNS resolution fails *before* `connect()`, so a hostname-based egress target shows up as a DNS query to the resolver, not the actual hostname. Hardcoded-IP malware produces a clean `connect <IP>:<port>` line. The **Sensitive File Access** list is the strong, unambiguous signal regardless.
252
+ - **Install-phase residual risk.** The install phase runs with network ON (it has to, to fetch packages). npm's supply-chain window is closed with `--ignore-scripts`; pip is pushed toward wheels with `--prefer-binary`. sdist-only packages still trigger a PEP 517 build — a known residual risk.
253
+ - **Native sandbox is Linux-only.** Bubblewrap doesn't exist on macOS/Windows. On those platforms, `--sandbox auto` falls back to Docker. The native sandbox also has no memory/CPU limits — use `--sandbox docker` for the full isolation profile.
254
+
255
+ ## FAQ
256
+
257
+ #### Why not just read the code myself?
258
+
259
+ You can — and you should, for repos you trust. But for the 95% case ("a stranger's repo with a flashy README"), reading every line of `setup.py` and `postinstall.sh` takes longer than running `repo-proofer`, and obfuscation can hide intent from a human reader. `repo-proofer` watches physics: if the app opens a socket, the kernel tells us. You can't obfuscate a syscall.
260
+
261
+ #### How is this different from Snyk / Socket / GitHub Advanced Security?
262
+
263
+ Those tools do *static analysis* — they read code to see if it looks malicious. `repo-proofer` does *dynamic execution* — it runs the code in a locked box and watches what it actually does. Static analysis is bypassable (obfuscated code, environment-triggered payloads). Dynamic execution is not: if a malicious repo needs to phone home to download its payload, it physically cannot do that inside `--network none`.
264
+
265
+ #### What does "BOOTS: YES" mean for a server that never exits?
266
+
267
+ A process that times out without crashing is a healthy long-running process (server, daemon, bot). The verdict is `BOOTS: YES (long-running)`. If it also printed a readiness signal (`"listening on port 8080"`, `"Uvicorn running"`), the verdict upgrades to `BOOTS: YES (server detected)` with the matched signal shown.
268
+
269
+ #### Can it run on macOS?
270
+
271
+ Yes, with Docker. `--sandbox auto` falls back to Docker on macOS (bubblewrap is Linux-only). `uvx repo-proofer <url>` works — it just needs Docker Desktop running.
272
+
273
+ #### Is it ready for production?
274
+
275
+ The engine is stable and the deterministic test suite (56 tests) passes on every commit. The native bubblewrap sandbox is new and should be considered beta — the Docker sandbox is the production-grade path. See the [CI badge](https://github.com/bootproof/repo-proofer/actions) for current status.
276
+
277
+ ## Contributing
278
+
279
+ Contributions are welcome. See the [test suite](scripts/smoke_test.py) for the deterministic core, and [tests/integration_test.py](tests/integration_test.py) for the Docker integration tests. Run `python scripts/smoke_test.py` to verify before submitting a PR.
280
+
281
+ ## License
282
+
283
+ [MIT](LICENSE)
@@ -0,0 +1,253 @@
1
+ # repo-proofer
2
+
3
+ [![CI](https://github.com/bootproof/repo-proofer/actions/workflows/ci.yml/badge.svg)](https://github.com/bootproof/repo-proofer/actions)
4
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+ [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
6
+ [![PyPI](https://img.shields.io/pypi/v/repo-proofer.svg)](https://pypi.org/project/repo-proofer/)
7
+
8
+ Find out if a repo will steal your keys before you run it.
9
+
10
+ GitHub is flooded with AI-generated "slop" — repositories with impressive READMEs that don't actually run, or worse, quietly phone home to a C2 server the moment you `npm install` them. `repo-proofer` clones a repo, drops it into a zero-network, read-only sandbox, executes it, and tells you — deterministically, no AI — whether it booted and whether it tried to read your SSH keys.
11
+
12
+ <p align="center">
13
+ <i>The slop-repo fixture being caught red-handed.</i>
14
+ </p>
15
+
16
+ ```
17
+ $ repo-proofer file://$(pwd)/tests/fixtures/slop-repo
18
+
19
+ ╭─ repo-proofer verdict ──────────────────────────────╮
20
+ │ Repository file://.../slop-repo │
21
+ │ Detected Stack Python │
22
+ │ BOOTS NO │
23
+ │ Detail exited 1 (crash) │
24
+ │ Network Egress BLOCKED │
25
+ │ Filesystem READ-ONLY │
26
+ │ Warnings [!] App crashed when network was │
27
+ │ blocked. May require external API │
28
+ │ to function. │
29
+ ╰──────────────────────────────────────────────────────╯
30
+
31
+ ╭─ Sensitive File Access (3) ──────────────────────────╮
32
+ │ - /etc/passwd │
33
+ │ - /root/.ssh/id_ed25519 │
34
+ │ - /root/.ssh/id_rsa │
35
+ ╰──────────────────────────────────────────────────────╯
36
+
37
+ [!] Sensitive file access detected — primary indicator of malicious intent.
38
+ ```
39
+
40
+ ## Highlights
41
+
42
+ - **Runs untrusted code safely.** Every execution is sandboxed with the network disabled and the filesystem read-only. The repo can't phone home. It can't write outside `/tmp`. It can't read `~/.ssh`.
43
+ - **Catches what static analysis can't.** Snyk, Socket, and GitHub Advanced Security read code to see if it *looks* malicious. `repo-proofer` runs it and watches what it *does*. Obfuscation can fool a linter. It cannot fool a kernel that refuses to open a socket.
44
+ - **100% deterministic, zero AI.** Pure subprocess + filesystem + strace. No LLMs, no API calls, no prompt-injection surface. Same answer every time, free to run forever.
45
+ - **No Docker required.** The default `--sandbox auto` uses a native bubblewrap sandbox on Linux — millisecond startup, no image pulls. Docker is the fallback for macOS/Windows or `--sandbox docker` for full clean-room isolation.
46
+ - **Three-color verdicts.** Green `BOOTS: YES` (it ran), red `BOOTS: NO` (it crashed or tried to steal secrets), yellow `NO RUNNABLE ENTRYPOINT` (it's a library, not slop). Libraries don't get the same red as malware.
47
+ - **Runtime Behavior Report.** strace traces every syscall inside the sandbox. You get an SBOM-style report based on *actual execution*: files read, files written, processes spawned, network calls attempted, sensitive paths touched.
48
+ - **Installable in one command.** `uvx --from git+https://github.com/bootproof/repo-proofer.git repo-proofer <url>` — no clone, no venv, no setup. Works today, straight from GitHub.
49
+
50
+ ## Installation
51
+
52
+ Run `repo-proofer` instantly with `uvx` — no clone, no venv, no setup:
53
+
54
+ ```bash
55
+ uvx --from "git+https://github.com/bootproof/repo-proofer.git" repo-proofer https://github.com/owner/repo.git
56
+ ```
57
+
58
+ That's it. `uvx` creates an ephemeral isolated environment, installs `typer`/`rich`/`GitPython`, clones the target repo, spins up the sandbox, runs the strace, prints the verdict, and cleans up after itself.
59
+
60
+ Once published to PyPI, the command shortens to:
61
+
62
+ ```bash
63
+ uvx repo-proofer https://github.com/owner/repo.git
64
+ ```
65
+
66
+ Other install methods:
67
+
68
+ ```bash
69
+ # Install permanently with pipx (from GitHub):
70
+ pipx install "git+https://github.com/bootproof/repo-proofer.git"
71
+ repo-proofer https://github.com/owner/repo.git
72
+
73
+ # Or from source (for development):
74
+ git clone https://github.com/bootproof/repo-proofer.git
75
+ cd repo-proofer
76
+ pip install -e .
77
+ ```
78
+
79
+ Requires Python 3.10+. The native sandbox (default on Linux) needs `bubblewrap` — install it with `apt install bubblewrap` (Debian/Ubuntu) or `dnf install bubblewrap` (Fedora). No Docker needed.
80
+
81
+ ## Documentation
82
+
83
+ See the [Limitations](#limitations) section for honest gaps, and the [FAQ](#faq) for common questions. The command-line reference is available with `repo-proofer --help`.
84
+
85
+ ## Features
86
+
87
+ ### Triage a repo
88
+
89
+ Point `repo-proofer` at any Git URL. It clones, detects the stack, installs deps, executes the entrypoint in a locked sandbox, and prints a verdict.
90
+
91
+ ```console
92
+ $ repo-proofer https://github.com/pallets/markupsafe.git
93
+ Cloning https://github.com/pallets/markupsafe.git (depth=1)...
94
+ Detected stack: Python | Native sandbox (bubblewrap)
95
+ Installing dependencies (network ON, timeout 60s)...
96
+ Executing entrypoint (network OFF, read-only FS, timeout 30s)...
97
+
98
+ ╭─ repo-proofer verdict ──────────────────────────────╮
99
+ │ Repository https://github.com/pallets/mar… │
100
+ │ Detected Stack Python │
101
+ │ BOOTS NO RUNNABLE ENTRYPOINT │
102
+ │ Detail no runnable entrypoint │
103
+ │ (looks like a library) │
104
+ │ Network Egress BLOCKED │
105
+ │ Filesystem READ-ONLY │
106
+ ╰──────────────────────────────────────────────────────╯
107
+ ```
108
+
109
+ `markupsafe` is a library — no `main.py`, nothing to run. The yellow verdict is correct: it's not slop, it just has no entrypoint. CI exits 0.
110
+
111
+ ### Catch a malicious repo
112
+
113
+ The `slop-repo` fixture impersonates an AI startup while quietly reading `~/.ssh/id_rsa` and `/etc/passwd`, then phoning home to a C2 server. Under `repo-proofer`'s `--network none` sandbox, the phone-home fails and strace catches the secret reads:
114
+
115
+ ```console
116
+ $ repo-proofer file://$(pwd)/tests/fixtures/slop-repo
117
+
118
+ ╭─ repo-proofer verdict ──────────────────────────────╮
119
+ │ BOOTS NO │
120
+ │ Detail exited 1 (crash) │
121
+ │ Warnings [!] App crashed when network was │
122
+ │ blocked. │
123
+ ╰──────────────────────────────────────────────────────╯
124
+
125
+ ╭─ Runtime Behavior Report ────────────────────────────╮
126
+ │ Files Read 2 │
127
+ │ Network Calls Attempted 1 │
128
+ │ Sensitive File Access 3 (see below) │
129
+ ╰──────────────────────────────────────────────────────╯
130
+
131
+ ╭─ Network Calls Attempted (1) ────────────────────────╮
132
+ │ - connect 203.0.113.42:443 │
133
+ ╰──────────────────────────────────────────────────────╯
134
+
135
+ ╭─ Sensitive File Access (3) ──────────────────────────╮
136
+ │ - /etc/passwd │
137
+ │ - /root/.ssh/id_ed25519 │
138
+ │ - /root/.ssh/id_rsa │
139
+ ╰──────────────────────────────────────────────────────╯
140
+
141
+ [!] Sensitive file access detected — primary indicator of malicious intent.
142
+ ```
143
+
144
+ Exit code 1. The verdict is unambiguous: this repo tried to steal your keys.
145
+
146
+ ### The sandbox
147
+
148
+ Two backends, same moat:
149
+
150
+ ```console
151
+ $ repo-proofer <url> --sandbox native # bubblewrap (Linux, no Docker, milliseconds)
152
+ $ repo-proofer <url> --sandbox docker # Docker (clean-room images, memory/CPU limits)
153
+ $ repo-proofer <url> --sandbox auto # default: prefer native, fall back to Docker
154
+ ```
155
+
156
+ Both backends enforce the same security constraints:
157
+
158
+ | Constraint | Docker mode | Native mode |
159
+ |---|---|---|
160
+ | Network | `--network none` | `--unshare-net` |
161
+ | Filesystem | `--read-only` + `--tmpfs /tmp` | `--ro-bind /usr` + `--tmpfs /tmp` |
162
+ | SSH keys | not mounted | `--tmpfs /home` + `--tmpfs /root` (empty) |
163
+ | Capabilities | `--cap-drop ALL` | bubblewrap drops all by default |
164
+ | Repo | `-v repo:/app:ro` | `--ro-bind repo /app` |
165
+
166
+ If the app crashes because it can't reach the network, **that is a successful detection of a hidden dependency, not a tool failure.**
167
+
168
+ ### Stack detection
169
+
170
+ `repo-proofer` detects the stack from marker files and resolves the entrypoint:
171
+
172
+ ```console
173
+ $ repo-proofer https://github.com/owner/my-cli.git
174
+ Detected stack: Python | Native sandbox (bubblewrap)
175
+ Executing entrypoint (network OFF, read-only FS, timeout 30s)...
176
+ ```
177
+
178
+ | Marker | Stack | Entrypoint resolution |
179
+ |---|---|---|
180
+ | `package.json` | Node.js | `scripts.start`, `main`, `bin`, then `index.js`/`app.js`/`server.js` |
181
+ | `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/` layout, `python -m <pkg>` |
182
+ | `go.mod` | Go (experimental) | `go run main.go` |
183
+ | `Cargo.toml` | Rust (experimental) | `cargo run --offline` |
184
+
185
+ A modern CLI that declares its entrypoint only in `[project.scripts]` (no `main.py`) is correctly detected as runnable — not mislabeled as a library.
186
+
187
+ ### Exit codes
188
+
189
+ ```console
190
+ $ repo-proofer <url>; echo "exit: $?"
191
+ exit: 0 # boots cleanly, OR is a library (yellow)
192
+ exit: 1 # crashed, or attempted sensitive file access (red)
193
+ exit: 2 # clone failed
194
+ exit: 3 # sandbox unavailable (no Docker / no bubblewrap)
195
+ exit: 4 # could not detect project stack
196
+ exit: 5 # failed to pull Docker image
197
+ ```
198
+
199
+ The exit code is CI-friendly: wire it into a GitHub Actions workflow and any repo that crashes or touches secrets blocks the PR.
200
+
201
+ ## How it works
202
+
203
+ ```
204
+ 1. Clone git clone --depth=1 (network ON)
205
+ 2. Detect filesystem checks for marker files (deterministic)
206
+ 3. Install sandbox ... <install_cmd> (network ON, 60s)
207
+ 4. Execute sandbox --network=none --read-only ... (network OFF)
208
+ └─ strace -ff -e trace=openat,connect,... (behavior report)
209
+ 5. Analyze regex on stdout/stderr + strace trace (deterministic)
210
+ 6. Verdict three-color panel + exit code
211
+ ```
212
+
213
+ No LLMs. No AI APIs. Pure subprocess + filesystem + strace. An LLM-based analyzer would be slower, more expensive, and gameable via prompt injection in the repo's own README. Pure determinism is the core feature.
214
+
215
+ ## Limitations
216
+
217
+ This tool is honest about what it can and can't do.
218
+
219
+ - **First run is minutes in Docker mode.** The Docker backend pulls base images and builds a strace image. The native backend (default on Linux) has no image pulls — it uses the host's runtimes and starts in milliseconds.
220
+ - **Go and Rust are experimental.** Both run under `--network none` with no install step, so projects with external dependencies can't fetch them at runtime. Only zero-dependency or pre-vendored Go/Rust projects boot.
221
+ - **Hostname-based C2 detection is indirect.** Under `--network none`, DNS resolution fails *before* `connect()`, so a hostname-based egress target shows up as a DNS query to the resolver, not the actual hostname. Hardcoded-IP malware produces a clean `connect <IP>:<port>` line. The **Sensitive File Access** list is the strong, unambiguous signal regardless.
222
+ - **Install-phase residual risk.** The install phase runs with network ON (it has to, to fetch packages). npm's supply-chain window is closed with `--ignore-scripts`; pip is pushed toward wheels with `--prefer-binary`. sdist-only packages still trigger a PEP 517 build — a known residual risk.
223
+ - **Native sandbox is Linux-only.** Bubblewrap doesn't exist on macOS/Windows. On those platforms, `--sandbox auto` falls back to Docker. The native sandbox also has no memory/CPU limits — use `--sandbox docker` for the full isolation profile.
224
+
225
+ ## FAQ
226
+
227
+ #### Why not just read the code myself?
228
+
229
+ You can — and you should, for repos you trust. But for the 95% case ("a stranger's repo with a flashy README"), reading every line of `setup.py` and `postinstall.sh` takes longer than running `repo-proofer`, and obfuscation can hide intent from a human reader. `repo-proofer` watches physics: if the app opens a socket, the kernel tells us. You can't obfuscate a syscall.
230
+
231
+ #### How is this different from Snyk / Socket / GitHub Advanced Security?
232
+
233
+ Those tools do *static analysis* — they read code to see if it looks malicious. `repo-proofer` does *dynamic execution* — it runs the code in a locked box and watches what it actually does. Static analysis is bypassable (obfuscated code, environment-triggered payloads). Dynamic execution is not: if a malicious repo needs to phone home to download its payload, it physically cannot do that inside `--network none`.
234
+
235
+ #### What does "BOOTS: YES" mean for a server that never exits?
236
+
237
+ A process that times out without crashing is a healthy long-running process (server, daemon, bot). The verdict is `BOOTS: YES (long-running)`. If it also printed a readiness signal (`"listening on port 8080"`, `"Uvicorn running"`), the verdict upgrades to `BOOTS: YES (server detected)` with the matched signal shown.
238
+
239
+ #### Can it run on macOS?
240
+
241
+ Yes, with Docker. `--sandbox auto` falls back to Docker on macOS (bubblewrap is Linux-only). `uvx repo-proofer <url>` works — it just needs Docker Desktop running.
242
+
243
+ #### Is it ready for production?
244
+
245
+ The engine is stable and the deterministic test suite (56 tests) passes on every commit. The native bubblewrap sandbox is new and should be considered beta — the Docker sandbox is the production-grade path. See the [CI badge](https://github.com/bootproof/repo-proofer/actions) for current status.
246
+
247
+ ## Contributing
248
+
249
+ Contributions are welcome. See the [test suite](scripts/smoke_test.py) for the deterministic core, and [tests/integration_test.py](tests/integration_test.py) for the Docker integration tests. Run `python scripts/smoke_test.py` to verify before submitting a PR.
250
+
251
+ ## License
252
+
253
+ [MIT](LICENSE)