Sandboxing
The boundary gets drawn before the first run, not after the first incident. For a code-executing agent a sandbox means four things: a read-only mount of exactly the data the job needs, no outbound network, no credentials anywhere inside, and a wall-clock limit that kills the run whatever it is doing. Sundry's reconciliation box is a container holding two CSV files, a writable output directory, a pinned Python image, 2 vCPU, 4 GB of memory, and nothing else.
How that container is actually built — images, layers, namespaces, cgroups, the difference between a container and a virtual machine — belongs to the Docker Deep Dive course, which teaches it properly and at length. This page owns the policy: which four constraints are load-bearing, which one every team leaves open, where credentials go instead, and what a correctly built sandbox still will not save you from.
The Four Constraints
Each constraint bounds a different kind of damage, and dropping any one of them makes the other three cosmetic. Filesystem scope bounds what a wrong program can read and destroy. Egress bounds what it can send. Credentials bound what it can authorize. Resource limits bound what it can consume, including the case where the program is fine and the data is forty times larger than last week.
| Constraint | Sundry's setting | What it bounds |
|---|---|---|
| Filesystem scope | /data read-only, two files; /out writable, empty at start | The blast radius is exactly the mount — nothing else is reachable to read or to ruin |
| Network egress | Default deny, allowlist empty for this job | Nothing leaves, whatever the program was told to do by whoever wrote the data |
| Credentials | None: no environment variables, no mounted key files, no instance identity | A program that gets everything else wrong still cannot authorize an action |
| Resource limits | 2 vCPU, 4 GB, 120 s per script, 60 min per run | A runaway loop costs two minutes of one core rather than a node |
The setting that surprises people is the empty allowlist. A reconciliation job needs no internet at all: the parent process has already placed the exports on the mount, and the finished file leaves the same way. When a job genuinely needs a remote source, the allowlist names that one host and nothing else, and every allowed destination is logged.
SANDBOX = {
"image": "sundry/recon-runtime:2026.08.1", # pinned, rebuilt via review
"mounts": [("/data", "ro"), ("/out", "rw")],
"env": {}, # deliberately, permanently empty
"egress": {"policy": "deny", "allow": []},
"limits": {"cpu": 2, "memory_mb": 4096,
"script_seconds": 120, "run_seconds": 3600},
"lifetime": "per_run", # destroyed on completion
}
Read that as a security document rather than as configuration. It states the image and its version, the two directories that exist and which of them can be written, that the environment carries nothing, that the network is closed with an empty exception list, the four ceilings, and that the container dies when the run does. Every one of those lines is a sentence a reviewer can argue with, which is the reason to keep the policy in one object instead of spread across a deployment manifest, a base image and somebody's memory.
Egress Is the One People Forget
An agent that can read data and reach the internet is an exfiltration path regardless of anyone's intent. Chapter 12 names the three legs of that path — private data, untrusted content, an outbound channel — and shows that removing any one of them closes it. The sandbox is where the third leg is cheapest to remove, because a batch job almost never needs to talk to anything. Default-deny with an explicit allowlist is the only sane setting, and it is the leg that turns an otherwise sound sandbox into a data-leak path when left open.
The reason it stays open is always the same and always reasonable-sounding: something in the program needs a package that is not in the image, the run fails on the import, and opening egress to the package index makes the failure go away in thirty seconds. Now the box can reach an arbitrary registry over the network, execute whatever it downloads, and post anywhere that registry's host resolution allows. Install dependencies at image build time, where a human reviews the list and a version gets pinned, and let the run fail loudly when the image lacks something. A failed run is a ticket; an open sandbox is an incident nobody has had yet.
Log every destination, including the allowed ones, and alert on volume. Sundry's ticket-analysis job has exactly one entry in its allowlist — the internal run-record API — and the log line for each connection carries the run id, the host and the byte count. That log has never caught anything. It exists so that the first time it does, the question "what left the box" has an answer rather than a theory.
Credentials Never Enter
The division of labour is simple and it does not bend: the sandbox computes, and the parent process holds credentials and performs every privileged action. The parent authenticates to the payment provider, pulls the export, writes it onto the read-only mount, starts the container, reads the artefact back through the output directory, verifies it, and posts it to the finance share under its own identity. The container has no idea any of those systems exist. Ceilings, rate limits and the audit trail all stay outside the box, which is the same argument Chapter 12 makes about tool dispatch, applied one layer down.
The temptation is real, because putting a key in the environment is one line and saves a round trip through the parent. What it costs is the boundary. Any program the model writes now holds that key, and so does any instruction that arrived inside the data the program is reading — a seller name, a CSV comment, a column header someone else controls. Chapter 12 calls that tool output as untrusted input, and it applies here exactly: the data is not neutral just because it arrived as a file. A sandbox with a credential in it is decoration around a process that can already do the damage.
Ephemerality
One container per run, destroyed when the run ends. Nothing survives between jobs except what you extracted on purpose, which means last week's temporary files, half-written outputs and whatever a failed script left behind cannot influence this week's numbers. It also means state cannot accumulate quietly: a box that has been alive for six weeks has a filesystem nobody has looked at and a package set nobody can reproduce.
The unit of ephemerality is the run, not the script. Sundry's reconciliation runs eleven or twelve scripts across its 41 minutes and they share one container, because step four legitimately reads the intermediate file step three wrote. Across runs the container is always new. That distinction is the whole discipline: within a run, shared state is the point; between runs, shared state is a defect waiting to be diagnosed. The corollary lands in the next topic — anything you want to keep must be copied out before teardown, because a destroyed container takes the deliverable with it.
What the Sandbox Does Not Protect Against
A sandbox is a containment control, not a correctness control, and confusing the two is how teams end up with a beautifully isolated wrong answer. Nothing about the mount, the network policy or the memory limit stops a program from dropping 400 rows on a bad join and printing a total that reconciles perfectly with itself. That is what the verification in Topic 59 is for, and it is a completely separate piece of engineering with a completely separate failure mode.
Nor does the box protect the systems it is legitimately allowed to reach. If the allowlist contains an internal API, a generated program will call it as fast as the runtime permits — no backoff, no concurrency cap, no notion that the endpoint is shared. The 2-vCPU limit does nothing about that; it caps what the box burns, not what the box asks other people to burn. Rate-limit each allowed destination at the egress proxy, and treat the allowlist as a list of dependencies you have taken on rather than a list of permissions you have granted.
- Mounting a working directory read-write with more than the job's data in it — the blast radius is the mount, so a repository checkout or a shared exports folder hands the box everything in it.
- Leaving egress open because a package install needs it — the fix takes thirty seconds and permanently converts the sandbox into a machine that can fetch code and send data.
- Passing an API key into the environment for convenience — every program the model writes now holds that key, and the boundary is decorative from that moment on.
- Reusing a container across runs — a stale intermediate file from last week's job is read by this week's step four, and the numbers are wrong in a way no error message describes.
- Default-deny egress with an explicit allowlist, log every allowed destination with the run id and the byte count, and rate-limit each entry.
- Keep credentials and privileged actions in the parent process, which fetches the inputs, extracts the artefact and publishes the result under its own identity.
- Run a fresh, ephemeral container per run with CPU, memory, per-script and per-run wall-clock ceilings.
- Build dependencies into a pinned image rebuilt through review, and let a run fail loudly when the image is missing something.
Knowledge Check
Which four constraints define a sandbox for a code-executing agent?
- Filesystem scope, network egress, credentials, and resource limits on CPU, memory and wall clock
- Image layers, namespaces, control groups, and the choice between a container and a virtual machine
- Language restrictions, an approved package list, static analysis of the code, and an execution timeout
- Human review of each script, full audit logging, an output size cap, and per-run cost accounting
Why does egress get singled out as the constraint teams most often leave open?
- It is the leg that turns a box holding private data into a path out of the company
- Network traffic from a sandbox is expensive and hard to attribute to a particular run
- An open network lets a program remount the filesystem read-write and edit the source data
- Remote calls make runs slow and unpredictable, which breaks the wall-clock ceiling
The reconciliation needs a fresh export from the payment provider. Where does that credential live?
- In the parent process, which fetches the export and writes it onto the read-only mount
- In the container's environment, scoped to read-only access so the exposure stays small
- On the read-only mount as a key file, so it cannot be modified by the running program
- In the system prompt, with an instruction never to write the value into any generated program
The sandbox is correctly configured on all four constraints. What can still go wrong?
- The program silently drops rows and produces a wrong file that reconciles perfectly with itself
- The program reads the ledger of a system outside the mount and copies it into the output
- The program uploads the refund export to an external host it discovered in the data
- A loop in the generated code consumes the node's CPU until the scheduler evicts other work
You got correct