The environment is the sandbox
Sandboxing one shell command was the right idea at the wrong scale. A coding agent operates a repository, package manager, compiler, dev server, and browser. Leave one outside and the boundary has a door in it.
Consider the apparently harmless task “upgrade the date library and fix the failing tests.” The agent edits one manifest and runs the package manager. The package manager resolves forty transitive dependencies. One of them executes a post-install script. The script reads the environment, probes the home directory, opens a socket, and exits successfully. The final diff contains a version bump and a lockfile. Nothing in those two files records the interesting part.
That is the scale mismatch. We tend to draw the risky object as the agent's shell command, while the work actually fans out into a temporary software supply chain. The command is only the first edge.
Command wrappers fit the world they were designed for. A program receives input, performs one bounded action, and returns output. You can put a wall around that moment.
A coding agent does not live in that world. It reads a repository, edits several files, invokes a package manager, starts a compiler, watches tests, launches a server, opens a browser, reads the console, and tries again. The work is a loop. Its children are part of the work.
If the agent is confined but its package scripts are not, the scripts own the machine. If the shell is confined but the browser uses your normal profile, the page inherits your sessions. If the repository is a worktree but the process still sees your home directory, checkout separation has been mistaken for authority separation.
Three boundaries that are too small
The model process
Watching only the agent executable assumes all consequential actions pass through its tool protocol. They do not. A build tool can spawn a compiler, which can invoke a linker, which can execute a helper. An install hook may run before the agent sees its next prompt. The process tree, not the first process, is the relevant object.
The command
Wrapping npm test helps only if every route to npm test uses the wrapper. An autonomous session makes hundreds of calls. Security that depends on the agent remembering the prefix is a convention, not a boundary.
The checkout
A Git worktree answers where edits land. It says nothing about ~/.ssh, cloud credentials, Unix sockets, the host network, or a browser profile. Git separates trees. It does not separate authority.
What belongs inside?
Put every component that can execute code or carry session state on the same side:
- Workspace: a disposable checkout with a pinned base.
- Agent and shell: one supervised process tree, including every child.
- Toolchain and dependencies: compilers, package managers, hooks, and caches.
- Dev server: reachable on the box's loopback, not accidentally published.
- Browser: a fresh profile that shares the box's network view.
That turns a scattered list of dangerous operations into one object with a lifecycle: create, work, inspect, export, remove.
A boundary needs an output gate
Containment is incomplete if the agent can write directly back to the repository you care about. The useful asymmetry is broad freedom inside and a narrow, human-operated path out.
h5i exports three artifacts: a path-validated patch, a human-readable report, and an execution receipt. The box cannot decide that its own result is acceptable. It can propose. A person chooses whether to carry the patch across.
Autonomy inside. Judgment at the boundary.
The boundary has to be cheap
If creating a box is a ceremony reserved for obviously dangerous work, ordinary work remains uncontained. That is why lightweight tiers matter. Under 200 milliseconds changes the decision from “is this risky enough?” to “why would this run anywhere else?”
Stronger boundaries still have a place. A container buys a portable filesystem. A microVM buys a separate kernel. The everyday path and the hostile-code path need not pay the same startup cost, but they should share the same lifecycle and output gate.
A practical test
Ask five questions of any agent sandbox:
- Where do package scripts execute?
- Which home directory and credentials can they see?
- Where does the dev server listen?
- Which browser profile opens the page?
- Can the agent write the accepted result directly?
If those answers cross the boundary in different directions, the sandbox is smaller than the work.
The boundary has to follow the process tree
A useful sandbox does not ask whether the current executable is called Claude, Codex, npm, cargo, or bash. Names are not security properties. It constrains the process tree that begins with the session.
That distinction matters the moment a tool delegates. A test runner starts workers. A compiler launches a linker. A package manager runs lifecycle hooks. A dev server invokes a bundler, which may invoke a native addon build. If confinement is implemented as a polite wrapper around the top-level command, the first child that does not use the wrapper has left the model.
h5i makes box shell and box run the entry points into a resolved policy. At the kernel tiers, filesystem and syscall restrictions are inherited. At supervised, the session also lives in a private network namespace with its own destination rules. At the image tiers, the whole tree starts inside the container or guest. The agent does not decide which child deserves the boundary. Children get it because they are children.
What escapes when one component stays outside
| Component left outside | What crosses the boundary | Why the diff stays quiet |
|---|---|---|
| Package manager | Install scripts execute as the host user | Reads, failed probes, and network attempts need not edit the tree |
| Dev server | Generated code and plugins run on the host | The server may only serve or transmit data |
| Browser | Host cookies, extensions, downloads, and loopback become reachable | Browser state lives outside Git |
| Agent configuration | Ambient credentials and privileged tool routes enter the session | Authority is configuration, not source |
| Output step | The subject of review can approve its own result | A direct write looks like any other edit |
The table is why “the agent itself is sandboxed” is not enough information. Ask where the work's other interpreters run. Every package hook, compiler plugin, test fixture, web page, and browser extension is another interpreter for input you may not control.
One object gives the work a reviewable lifecycle
Once the environment is the object, the workflow becomes easier to reason about:
- Create: freeze the Git base, resolve the profile, and hash the policy before writable state exists.
- Work: let the agent edit, build, run services, and use the browser within that policy.
- Observe: record process exits and boundary decisions outside the agent's write path.
- Review: compare the final tree with the pinned base and read execution evidence beside it.
- Export or apply: move one reviewed result across a human-operated gate.
- Remove: discard the workspace without turning it into a permanent pet environment.
The frozen base and policy digest are more than metadata. They prevent the meaning of “this run” from drifting. If the parent branch moves or the policy file changes later, the box still names the code and rules it actually started with.
What this design does not claim
A complete boundary can still have a weak tier. workspace gives checkout hygiene and no process confinement. Every tier below microvm shares the host kernel. A container's HTTP proxy cannot constrain a raw socket that ignores it. The boundary is one object; its strength still depends on the mechanism chosen for that object.
Containment also does not certify the patch. A malicious or simply wrong agent can produce code that passes the tests it chose to run. The output gate creates a place for judgment; it does not automate judgment away.
And no local sandbox can stop source from entering a model request that policy legitimately permits. If source must not leave, the answer is a self-hosted model or no model egress—not stronger language around the same allowed API call.
Security becomes normal only when disposal is economical
There is an operational reason integrated environments beat a checklist of wrappers. Developers stop using expensive safety mechanisms for ordinary work. If every agent session requires building an image, negotiating a remote worker, and waiting minutes for dependencies, the box is reserved for code already known to be dangerous. Most supply-chain surprises arrive in code nobody preclassified that way.
The lightweight tiers attack startup cost. Warm caches attack dependency cost without creating a writable rendezvous between boxes: cache content is keyed by lockfile state, populated by a dedicated refresh job, and mounted read-only into agent work. The browser and dev server start inside the already-created boundary, so testing a web change does not require publishing a host port or attaching to a daily browser.
Disposal matters at the other end. A long-lived development container accumulates credentials, caches, debugging exceptions, and manual fixes until nobody can state its boundary. A box has a frozen base, one resolved policy, one purpose, and an expected end. Export what deserves to survive. Remove the rest.
This gives containment a property security tooling rarely gets: the safer workflow is also easier to reason about. One name identifies the workspace, policy, process tree, browser, receipts, and cleanup target. There are fewer ambient pieces for both the agent and the reviewer to misunderstand.
Sources and further reading
- The h5i manual: the loop, for the command-level lifecycle.
- Isolation tiers, for the enforcement and limits of each boundary.
- The first-box guide, for running the complete loop on a real repository.
- The project README, for the current product claim and explicit non-claims.
Questions that come up
Is a Git worktree an agent sandbox?
Why does the browser need to be inside?
Five tiers, five promises
Choose an isolation mechanism by the threat it changes.
Try the whole loop once
Create a box, do one real task, and review the patch beside the execution record.