Lab 01 — Capability-Gated Sandbox Executor
Phase 09 · Lab 01 · Phase README · Warmup
The problem
An agent wants to run a model-proposed action — read a file, write a file, fetch a URL, call a tool. The action is untrusted: it came out of a stochastic model that may have been steered by an injected instruction in a document it read (Phase 10). "The model proposes, the application executes" (Phase 00) — and this lab is the executes clause done safely.
You will build a pure-Python model of the containment boundary a real sandbox enforces at the kernel — a container / gVisor / Firecracker microVM. The whole thing turns on one invariant:
A denied operation returns a structured result — it NEVER raises, and it NEVER performs the side effect. No partial write, no network packet, no escape.
Concretely: a Sandbox checks every op against a granted Capabilities object before
performing it. Reads/writes are confined to allow-listed path prefixes (with a path-traversal
defense), egress is confined to allow-listed hosts (the anti-exfiltration control), tools to an
allow-list, and three resource budgets (max_ops, max_output_bytes, max_wall via an
injected clock) cap a runaway. A SandboxedAgent drives a whole op sequence and stops on a hard
limit — proving a hostile sequence cannot escape the grant.
What you build
| Piece | What it does | The lesson |
|---|---|---|
Capabilities | frozen allow-lists (fs_read, fs_write, net_hosts, allowed_tools) + budgets | least privilege / POLA; allow-lists fail closed; authority is immutable |
VFS / Net | in-memory filesystem + network with a calls log | testable, offline side effects; the log proves "no exfil on denial" |
has_traversal / canonical / under_allowlist | the path defense | canonicalize-then-check; /work must not authorize /workshop |
Sandbox.run(op) | charge budgets → check capability → then perform | check-before-effect; denials never raise, never mutate |
_charge_output, max_wall clock | the resource meters | a blown budget is a hard halt (the cgroup/breaker) |
SandboxedAgent.run_all | drives a sequence, stops on a hard limit | a hostile op sequence is contained |
File map
| File | Role |
|---|---|
lab.py | your implementation (fill the # TODOs) |
solution.py | reference implementation + worked example (python solution.py) |
test_lab.py | the proof — 26 tests: allow/deny FS, traversal, egress, tools, budgets, no-side-effect, driver, determinism |
requirements.txt | pytest only |
Run it
pytest test_lab.py -v # your lab.py (red until you implement)
LAB_MODULE=solution pytest test_lab.py -v # the reference (green)
python solution.py # worked example
Success criteria
-
A read/write inside the allow-list is allowed; outside it is denied — and a denied write
leaves
VFS.snapshot()byte-for-byte unchanged. -
A
..path is denied aspath_traversal, and/workdoes not authorize/workshop. -
A fetch to an allow-listed host works; a fetch to any other host is
egress_deniedand never appears inNet.calls(no exfil). -
max_ops,max_output_bytes, andmax_walleach deny with the right violation and ahard=Truehalt; a capability denial ishard=False(soft). -
No denial ever raises; every denial is a
SandboxResult(ok=False, violation=...). -
SandboxedAgent.run_allstops on the first hard limit; a hostile sequence is contained. -
All 26 tests pass under both
labandsolution.
How this maps to the real stack
Capabilitiesis the OCI security config:--cap-drop=ALL,--read-only,--network=none+ egress allow-list,--pids-limit,--memory, aseccompprofile. Least privilege, one flag at a time; frozen because dropped authority can't be re-acquired.- The
fs_read/fs_writesplit models a mount namespace + read-only rootfs + a writable scratch mount.canonicalize-then-checkis the userspace belt;pivot_rootinto the image is the kernel's suspenders. - The
net_hostsallow-list models a network namespace + KubernetesNetworkPolicy(default-deny egress). The egress check runs before the fetch — the anti-exfiltration guarantee. max_ops/max_output_bytes/max_wallmodel cgroups (pids,memory,cpu) + an execution deadline; the hard halt models an OOM-kill / throttle. The injected clock is the same replay-safe seam as Phase 08.- The whole
Sandboxis the model of a container / gVisor / Firecracker boundary; in prod you'd run untrusted agent code in an ephemeral Firecracker microVM (Vercel Sandbox, E2B) or a hardened gVisor pod — one per task, thrown away.
Limits of the miniature. This is an in-process model of a capability check — not a
security boundary. Never run actually-untrusted code against it; real isolation is the kernel
(namespaces/cgroups/seccomp) or a hypervisor (microVM), not a Python if. It also omits
symlink/TOCTOU races, syscall filtering, and side-channels. What transfers is the shape of
every control — check before effect, allow-list, fail closed, bound the budget — which is
identical from this if up to Firecracker.
Extensions (your own machine)
- Run it for real. Put the same eight ops behind a Docker container:
--read-onlywith a--tmpfs /work/out,--network=none(or an egress proxy),--pids-limit 64,--memory 256m,--cap-drop=ALL, and a seccomp profile. Watch the OS enforce what yourifmodeled. - gVisor / microVM. Swap the runtime to
runsc(gVisor), or run the snippet in E2B / Vercel Sandbox and compare cold-start latency and syscall behavior. - SSRF hardening. Extend
Netto resolve hostnames to IPs and add link-local/RFC-1918 denial, so169.254.169.254(cloud metadata) and internal ranges are blocked even when a hostname slips through. - Symlink/TOCTOU. Add a symlink concept to
VFSand show how a check-then-open race defeats canonicalize-then-check — then fix it by resolving symlinks inside the jail.
Interview / resume signal
"Built a capability-gated execution sandbox for agent tool/code calls — least-privilege allow-lists for filesystem, egress, and tools; canonicalize-then-check path-traversal defense; default-deny egress as the anti-exfiltration control; and cgroup-style resource budgets (ops/output/wall) with hard-halt semantics — modeling the container/microVM boundary (namespaces, cgroups, seccomp) so a hostile, injection-driven op sequence is contained with zero side effects."