# Teaching Script — Module B7: Sandboxes and Execution Controls

**Course**: Course 2B — Securing & Attacking Harnesses and LLMs
**Module**: B7 — Sandboxes and Execution Controls
**Duration**: ~35 minutes (spoken at ~140 wpm)
**Format**: Verbatim transcript with `[SLIDE N]` cues. Read aloud or use as speaker notes.

---

[SLIDE 1 — Title]

Welcome to B7. Sandboxes and Execution Controls. This is the module where we stop pretending the sandbox is a checkbox and start treating it as the last line of defense — because it is. Injection will succeed. We have spent six modules establishing that. The agent will be coerced into running code. The sandbox is the layer that decides whether that means a confused log line or a reverse shell on your jump host. Design it as if the sandbox will be escaped — because the same vulnerability class that lets the agent run attacker code will, eventually, let that code walk out of the sandbox.

[SLIDE 2 — The inversion from Course 1 Module 5]

If you came from Course 1, you had a Module 5 on sandboxing and execution isolation. That module taught you how to build a sandbox — the inside-versus-outside split, the provider table, filesystem and network scoping. This module inverts the lens. In Course 1, the threat was a misbehaving-but-honest agent. An over-long loop. A mis-scoped write. The sandbox contained accidents. In 2B, the threat is an agent under prompt-injection coercion, induced to run attacker-chosen code inside your sandbox. The difference is the adversary. A sandbox tuned to survive "the agent wrote to dot-ssh by accident" is not tuned to survive "the agent was instructed to compile and run a C implant that exploits a CVE in your runtime."

[SLIDE 3 — The inversion, restated as a philosophy]

Here is the design philosophy of this module, and it inverts the naive intuition. The naive intuition is: harden the sandbox so it cannot be escaped. That is the wrong goal. Every sandbox is software. Every sandbox has CVEs. Every sandbox will, eventually, be escaped by an adversary who controls the code inside it. The V8 isolate, the Firecracker microVM, gVisor's runsc — all have had escape-class bugs, all will have more. The correct goal is: assume the sandbox will be escaped, and design so that an escaped sandbox still has no credentials and no network. The escape becomes a detected-and-killed anomaly, not an incident. The value of escape is what you can reach after escaping. Design so the answer is nothing.

[SLIDE 4 — B7.1 The sandbox hierarchy]

Sub-section one. The sandbox hierarchy and the escape threat. Where your sandbox sits on the isolation curve, and the four ways an adversary controlling the agent will attack it.

[SLIDE 5 — The isolation hierarchy]

The isolation hierarchy, weakest to strongest. Level one, OS process isolation: the agent runs in a separate process, isolation is the kernel's process boundary plus UID separation. Cold start is instant, blast radius is the entire user account. Fine for a dev tool, catastrophic for an agent running attacker code. Level two, containers — Docker, Podman. Linux namespaces plus cgroups plus a layered filesystem. The boundary is the Linux kernel, which is CVE-rich. This is where most production harnesses start and, dangerously, where many stop. Level three, hardened containers — gVisor implements a userspace kernel that intercepts syscalls, Kata runs the container inside a lightweight VM. These raise the escape bar substantially. For an agent sandbox that will run attacker-influenced code, plain Docker is a defect; gVisor or Kata is the floor. Level four, microVMs — Firecracker, AWS's minimal KVM-based VM. About 125 milliseconds cold start, a stripped device model. The escape surface is small and aggressively audited. This is what you reach for when the sandbox runs genuinely untrusted code. Level five, WASM and V8 isolates — language-level sandboxing, confined to linear memory and a capability-based import table. Cold start in milliseconds. This is what IronCurtain's Code Mode uses. Level six, full VMs — strongest isolation short of separate hardware, almost never the right answer for an agent sandbox because microVMs give you 95 percent of the isolation at 5 percent of the cost. The reference implementation here is NemoClaw's OpenShell — the agent never touches the sandbox directly, every execution goes through the governed API.

[SLIDE 6 — The four attack vectors]

When injection succeeds and the agent is coerced into running attacker-chosen code inside the sandbox, four vectors by which that code attacks the system, not just the sandbox. Vector one, sandbox escape: the contained code exploits a bug in the provider itself — V8 type confusions, container breakouts via kernel CVEs, gVisor syscall-emulation bugs. The control is twofold — pick a stronger point on the hierarchy, and assume the escape succeeds, which is the blast-radius principle. Vector two, network egress: the sandbox runs the code fine — too fine. The code reaches the network, exfiltrates data, callbacks to a C2, SSRFs the cloud metadata service. This is the most under-applied control. The fix is default-deny egress, which we cover next sub-section. Vector three, resource exhaustion — OWASP ASI09. The code is induced to run forever, fill the disk, exhaust memory, fork-bomb. The fix is hard resource caps. Vector four, sidecar compromise: the sandbox spawns a helper — a language server, a package installer — that runs with more privilege than the contained code. The code suborns the helper. The fix is privilege minimization on every helper. Note that vector two is the one operators ignore. They fixate on the filesystem mis-scope that coughs up the secret and ignore the network that carries it out. Default-deny contains the larger class of outcomes.

[SLIDE 7 — B7.2 Execution policies and network segmentation]

Sub-section two. Execution policies and network segmentation. What code the agent is allowed to run, and what the sandbox is allowed to reach.

[SLIDE 8 — Allowlists, not denylists]

The foundational rule of execution policy: allowlist, do not denylist. A denylist — block curl, wget, netcat, python -c — is a list of things you have thought of. The attacker finds wget. You add wget. The attacker finds python with urllib. You add that. The attacker finds perl, then git clone with a hook, then directly invoking libc. Each addition is one more command you have thought of, and the space of things that fetch a URL or run code is unbounded. An allowlist — ls, cat, grep, git status, pytest — is a list of things the task needs. The attacker's job becomes: make an allowed command do harm via argument injection or a vulnerable subcommand. That is a bounded, auditable surface. The stronger pattern is per-tool execution scopes. The run-tests tool may invoke pytest and make. The format-code tool may invoke prettier and black. The install-deps tool may invoke pip install but only through the validating proxy. Each tool carries its own allowlist, and the sandbox enforces that the command matches the calling tool's scope before exec. That is how you prevent a coerced agent from invoking pytest from inside the install-deps tool.

[SLIDE 9 — Deterministic enforcement]

How is the allowlist specified? Two schools, and they are the same debate that runs through the CrabTrap and IronCurtain deep dives. School one: LLM-as-judge at runtime. Each tool invocation is evaluated by an LLM that sees the command, the arguments, and the policy, and returns allow or deny. Flexible — novel attacks are caught because the judge understands semantics. Probabilistic — a prompt injection in the arguments can manipulate the judge. For security-critical decisions, probabilistic is a defect, not a feature. School two: deterministic compilation, the IronCurtain model. Policy is written in plain English, compiled offline by an LLM into deterministic JSON rules. At runtime, enforcement is pure if-then matching. Zero LLM at runtime. The LLM's probabilism is confined to build time, where a miscompilation can be caught by the verify-and-repair pipeline. The principle: security-critical runtime decisions must be deterministic. The allowlist for a tool's command surface is a finite, enumerable set. There is no semantic question the LLM needs to answer at runtime. Reserve LLM-as-judge for the arguments if you use it at all, and even there prefer structured validators — regex, schema, path-prefix checks — over an LLM.

[SLIDE 10 — Default-deny network egress]

The sandbox should have no network egress by default. This is the single most under-appreciated control in shipped agent sandboxes, and it is the one that contains the worst outcomes. The sandbox's network namespace starts with egress denied to everything. Each task or each tool carries an allowlist. The web-search tool reaches one host. The fetch-docs tool reaches a doc host. The install-deps tool reaches the validating registry and nothing else. Enforcement is at the network layer — iptables or nftables in the namespace, an egress proxy the sandbox is forced through, or the microVM's virtual NIC restricted to allowlisted destinations. Not at the LLM-judge layer. This is distinct from CrabTrap. CrabTrap governs egress at the LLM-judge layer — every outbound request evaluated against a natural-language policy. That is valuable for the semantic question, is this request something this agent should be making. But it is the wrong tool for the existence question, should this sandbox reach the network at all. The network-layer default-deny answers the existence question deterministically. CrabTrap answers the semantic question probabilistically on top. You want both, and the network-layer control is the one that holds when the LLM judge is bypassed, confused, or absent. And the metadata service deserves special mention. 169.254.169.254 hands out instance credentials to anything that can reach it. A sandboxed agent that can reach metadata can steal the host's cloud role — a far worse outcome than reading a local secret. Block it at the network layer, always.

[SLIDE 11 — B7.3 Sidecar monitors and the blast-radius principle]

Sub-section three. Sidecar monitors and the blast-radius principle. The monitor that watches the sandbox, and the design discipline that makes escape survivable.

[SLIDE 12 — The sidecar monitor]

A sandbox with no monitoring is a sandbox whose compromise you discover from an incident report. A sidecar monitor is a process that observes the sandbox from outside its trust boundary — the contained code cannot reach it to disable it — and flags anomalous behavior. The monitor watches four signal classes. Resource signals — CPU, memory, disk, process count. A spike is the signature of resource exhaustion or an escape in progress. Syscall signals, where observable — seccomp-bpf, eBPF, gVisor's syscall log. A grep invocation that issues socket, connect, and fork is not behaving like grep. Network signals — connection attempts, allowed and denied, DNS queries, byte volumes. Denied-connection bursts are especially informative; they are the contained code trying to reach something the policy blocked. Behavioral signals — repeated failures, unusual command sequences, git invoked from inside the pytest tool. The monitor's output feeds B8's observability pipeline, correlated with the agent's tool-call trace, so an investigator can reconstruct: coerced at step 14, ran the command at step 15, egress denied at step 16, flagged at step 17. The monitor detects. The controls in B7 dot 2 prevent. The two are complementary.

[SLIDE 13 — The blast-radius principle: three realizations]

The blast-radius principle, made concrete in three realizations. One: no credentials in the sandbox. The sandboxed process runs with no API keys, no cloud-instance role reachability, no secrets in its environment. This is IronCurtain's credential-quarantine model — the agent is configured with fake keys, and a TLS-terminating MITM proxy hot-swaps fakes for reals at the boundary. Even a fully-compromised, fully-escaped agent cannot exfiltrate real credentials because it does not have them. Two: no network from the sandbox. Even after escape, the contained code is in the sandbox's network namespace or the microVM's virtual NIC, which is default-deny with per-task allowlists. An escape that reaches the host kernel does not magically gain a new network interface. Three: no privileged helpers. Every subprocess runs with minimum privilege — no root, no capabilities beyond what the task needs, no ambient authority. The contained code that escapes and tries to suborn a helper finds helpers that are themselves locked down. If your architecture requires credentials in the sandbox — the inside-sandbox model from Course 1 Module 5 — you have accepted a worse blast radius. Document the decision and reinforce the other two.

[SLIDE 14 — Reference architectures]

Three reference architectures, three layers. OpenShell, from NemoClaw, deep dive 09 — the reference sandbox. The agent never touches the sandbox directly; execution goes through the governed API. CrabTrap, deep dive 19, from Brex — the LLM-as-judge egress proxy. The semantic egress layer; complements, does not replace, network-layer default-deny. IronCurtain, deep dive 20, from Niels Provos — deterministic enforcement plus credential quarantine. The reference defense architecture. Zero LLM at runtime, agent never holds real keys. Three projects, three layers. Together they approximate the blast-radius principle: OpenShell brokers execution, CrabTrap judges semantics, IronCurtain quarantines credentials and compiles policy deterministically.

[SLIDE 15 — Lab and what's next]

The lab has you build the Sandbox Policy Enforcer — an allowlist command gate with per-tool scopes, a cgroup-enforced resource-cap enforcer, and a default-deny egress gate with per-task allowlisting. Then, at the end, you attempt to reach the network from inside the sandbox and watch the egress gate deny it. That moment — the gate denying your escape attempt — is the entire module in one demonstration. Next: B8, Observability and Attack Detection. The sidecar monitor's events feed B8's pipeline. Detection is the layer that tells you when the B7 controls engaged on a real attack, and reconstructs the coercion-to-exfiltration timeline so you can prove what happened, when, and through which control. Let's build it.
