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

  1. Paste the attack preset → BLOCKED, rule secret_path_read, receipt names the SSH path.
  2. 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.
  3. Paste the clean preset (total([10, 20, 30])) → PASSED, cage output 60, approval button appears.
  4. Try to approve a BLOCKED receipt → 403. Blocked cannot be approved.
  5. 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.server web app, deterministic policy engine, child-process runtime cage, append-only audit log.
  • Deployment: Vercel serverless (Hobby, free, no card). The local http.server app 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 /tmp on 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.py outside 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

Share this project:

Updates

Submission history