Inspiration
You paste AI-written code into your repo every day. That code runs as you, with your credentials — and the sandbox setting you trust is a setting the agent can switch off. The moment you hesitate before pasting is the moment something can go wrong: one open('/home/dev/.ssh/id_ed25519') in an AI suggestion, and your private key is read by code you never reviewed.
Developers have no fast, visible way to answer one question before they paste: what will this code actually try to do?
What it does
CageCheck is a cage whose policy lives outside the cage. Paste a snippet, it is checked in a throwaway cage, and every block comes with a receipt naming the exact path, rule, and replay instruction. A clean snippet is the only snippet that can be approved — a BLOCKED snippet cannot be approved at all (the approve endpoint returns 403).
- Deterministic policy, fail-closed: secret-path reads, network connects/egress, subprocess/shell, destructive calls, secret env reads, writes/reads outside the cage. Unlisted external effects are blocked. A network import with no connect attempt is HELD for human review.
- Runtime cage: the snippet actually runs in a restricted cage (restricted imports/open, secret-scrubbed environment, 5 s / 256 MB / 1 MB limits) — the policy is checked before dispatch and enforced at runtime.
- Receipt for every check: receipt ID, timestamp, stamp (PASSED / HELD / BLOCKED), policy rule, redacted evidence, cage tier, replay instruction, and an explanation. A block you can quote back.
- Nothing is sent anywhere. Approval means "cleared to copy", not "sent". The cage never receives credentials.
The safer outcome
A developer pastes an AI suggestion that quietly reads /home/dev/.ssh/id_ed25519. Without CageCheck, that read happens the moment the code runs — with the developer's own credentials. With CageCheck, the snippet is BLOCKED before it touches the repo, rule secret_path_read, receipt CC-20261103-123520-472D names the exact path, and the developer sees a safe replacement suggestion instead of a silent compromise.
Demo
- Paste the attack preset → BLOCKED, rule
secret_path_read, receipt names the SSH path. - Paste the policy-toggle preset (
open('/app/policy.json', 'w')) → BLOCKED, write outside the cage. The policy file itself cannot be rewritten from inside the cage. - Paste the clean preset (
total([10, 20, 30])) → PASSED, cage output60, approval button appears. - Try to approve a BLOCKED receipt → 403. Blocked cannot be approved.
- Past receipts list + health endpoint show the audit trail.
How we built it
- Stack: Python standard library only — no dependencies, no build step, no CDN, no webfonts.
http.serverweb app, deterministic policy engine, child-process runtime cage, append-only audit log. - Deployment: Vercel serverless (Hobby, free, no card). The local
http.serverapp was adapted with a thin WSGI entrypoint (api/index.py) — the gate logic, policy, cage, and receipts are byte-identical to the local app; only the transport changed. The audit log lives in/tmpon the serverless host, so receipts are per-instance and ephemeral (a fresh instance starts with an empty receipt list); the verdict, receipt, and 403-on-BLOCKED-approve behaviour are identical. - Design: paper/ink/stamp — a border inspector's stamp desk, not a hacker's terminal. One screen, one input, one primary action, readable in one scroll. Stamp colours are never colour-only: the word PASSED / HELD / BLOCKED is always printed.
- AI assistance: the optional explanation of a finding can come from Vultr Serverless Inference; it NEVER decides block/approve. Without credentials it is labelled
[local mock — no Vultr Inference call]. - Built entirely 3 Nov 2026, inside the SafeSpace build window (17 Oct–22 Nov 2026). No pre-existing components beyond the Python standard library.
Challenges we ran into
- Making the policy live outside the cage so a snippet cannot rewrite its own rules — solved by keeping
policy.pyoutside the cage directory and blocking writes outside it (verified: the policy-toggle preset is BLOCKED). - Fail-closed vs. usable: a network import with no connect attempt is HELD rather than blocked, so legitimate code isn't punished for an import alone — but HELD still needs human review before approval.
- Honest boundaries: locally this is process + patch isolation, not a kernel/container boundary. The real boundary is a throwaway Docker container per check (Dockerfile in the repo); we state this plainly rather than overselling the local cage.
Accomplishments that we're proud of
- 10/10 automated tests pass (policy rules, runtime cage, redaction).
- A BLOCKED snippet is unapprovable by design — the 403 is a feature, not a bug.
- Every decision is quotable: receipt ID, rule, evidence, replay.
What we learned
- A safety tool must not create new risk: no exfiltration path, no credentials in the cage, no silent failures.
- Simplicity is a safety feature: one screen means a judge — and a developer in a hurry — sees the whole decision at once.
- Stating limitations honestly builds more trust than claiming guarantees: CageCheck reduces risk, it does not guarantee safety.
What's next
- Live deployment with the per-check Docker container boundary (the local cage becomes a real container boundary).
- The planned CSS-3D "cage view": the snippet visibly enters the cage, policy rules appear as cage bars, a blocked attempt stops at a bar.
- Keyboard/focus and labelling accessibility fixes (textarea label, loading state, duplicate-submit guard).
Limitations
Locally, the cage is process + patch isolation, NOT a kernel/container boundary. Until the Docker-per-check deployment is live and verified, treat CageCheck as a working prototype that demonstrates the policy, receipts, and approval gate — not as a production sandbox. It reduces risk; it does not guarantee safety, and it is not a replacement for code review or emergency services.
On the live deployment, receipts are per-instance and ephemeral: the serverless host keeps the audit log in temporary storage, so a fresh instance starts with an empty receipt list and past receipts do not survive restarts. Every individual check still produces a full, quotable receipt at check time; only the shared history is ephemeral.
Credits
- Built solo by M S Abhishek (Shobhit University, Meerut, India).
- Design origin: CageCheck was designed for Vultr Agent Rush Challenge 1 ("Blast Radius Zero"). That entry was never submitted; all code was written 3 Nov 2026, inside the SafeSpace build window, and no code was copied from any earlier project (including the 2 Nov Containment Receipt Agent).
- No third-party datasets, assets, or pre-existing components beyond the Python standard library. No credentials, private data, or live vulnerabilities are exposed anywhere in the demo; all examples are synthetic.
Built With
- css
- html
- javascript
- python
- vercel