Sandbox the entire workflow: browse, develop, review, apply
A browser and a coding sandbox are not two adjacent workflows. Put the browser, checkout, agent, tools, and dev server in one box; review what crossed that boundary before you apply the result.
A coding agent rarely stays in an editor. It reads documentation, installs dependencies, runs tests, starts a local application, opens that application in a browser, follows an error back into the code, and tries again. Sandboxing only the shell while the browser runs on the host splits one job across two security boundaries.
That split is easy to miss because browsing and code development have different interfaces. They are still one authority problem. The page can influence the agent. The browser can hold cookies and reach network destinations. The dev server can expose the code the agent just changed. If one of those pieces sits outside the box, the workflow is only partly contained.
| Phase | Inside the box | Outside the box |
|---|---|---|
| Browse | Fresh browser profile, page state, cookies, network client | Policy, request observation where the tier supports it |
| Develop | Checkout, agent, package scripts, tests, dev server | Credential broker and resolved policy |
| Review | The proposed tree stays unchanged | Human reads diff, denials, commands, and browser evidence |
| Apply | No direct write path to the parent repository | Human exports or applies the accepted patch |
1. Start with one boundary
Create a named box from the repository before opening the browser or starting the agent. The browser profile supplies a fresh browser identity and the control path needed to operate it inside the box.
$ h5i box --profile browser --engine h5i --isolation process --name fix-auth
$ h5i box status fix-authCreation freezes the base revision and resolves the policy before the workspace exists. status tells you which isolation tier the host actually provided, which paths are writable, how network access is scoped, and the digest that later receipts carry. If the requested policy cannot be enforced, creation fails instead of quietly substituting a weaker tier.
The source determines the output path. A box made from the current repository is a worktree on its own branch, so an accepted result can later be applied locally. A URL, pull request, or --new produces a detached box. Detached work can be exported as a patch, but apply and rebase refuse because that box has no authority over the parent repository.
| Tier | What confines the code | Egress scoping |
|---|---|---|
workspace | A separate worktree, no confinement | none |
process | Landlock, seccomp, namespaces; a supervisor and a private pid namespace | deny or host |
supervised | The above plus a private netns and a seccomp-notify gate on socket() | L3/L4 |
container | Rootless Podman on a portable image | L7 proxy |
microvm | A guest with its own kernel, booted by microsandbox | L3/L4 in the guest |
The example names process because it can hold a resident browser without a heavyweight runtime. The tier changes what “inside” proves. A process-tier browser is confined for files and environment, but its request log remains the engine's own account. A tier with egress enforcement outside the browser can add host-observed network evidence. Resident browser sessions also need a tier that can keep the engine alive; on Linux today, a microVM is the tier that provides both residence and a network boundary. Read status rather than inferring guarantees from the word sandbox.
2. Put browsing inside the same box
This flag connects the two workflows:
$ h5i browser open https://docs.rs/ --allow docs.rs --in fix-auth
ok browser session br_7k2xqa
placed : box fix-auth
$ h5i browser snapshot
$ h5i browser click @e3
$ h5i browser requests--in fix-auth places the browser engine and its fresh profile in the named box. Later browser verbs address the same resident session, so the page state, cookies, downloads, and requests stay with the development environment instead of appearing in a host browser profile.
The distinction matters in both directions. External documentation can contain instructions that influence the agent, so it should not gain more filesystem or network authority than the coding task. Later, when the browser opens http://localhost:3000, loopback should mean the dev server inside the box—not some unrelated service on the developer's machine.
The browser still checks its origin policy before every request and records the decision before bytes move. Denied requests and refused redirects remain in the log. When the box tier enforces egress outside the engine, h5i can label that traffic host-observed. Without an outside network observer, it remains engine-claimed. Placement and evidence strength are related, but they are not the same claim.
3. Develop and verify without crossing the boundary
Enter the same named box for the coding session:
$ h5i box shell fix-auth
box$ claude # or codex
box$ npm ci
box$ npm test
box$ npm run dev &shell inherits stdio, and every descendant stays inside the resolved policy. The agent does not have to remember to wrap package-manager hooks, compiler workers, test processes, or the dev server separately. They are contained because they are children of the box session.
Now point the already-contained browser at the application. From a second host terminal:
$ h5i browser open http://localhost:3000 --in fix-auth --session app --new
$ h5i browser snapshot --session app
$ h5i browser requests --session appThe browser and server meet on the box's loopback. The useful loop is now continuous: the agent edits, tests, starts the app, reads the page, inspects failed requests or console errors, fixes the code, and tests again. There is no host-browser detour in the middle.
If the agent invokes the browser from inside an existing box shell, it opens the session without --in; it is already in the box. The flag is for a host-side command that places a browser into a box the caller stands outside. h5i refuses --in from inside rather than pretending to add a second boundary.
Model API keys remain on the host. A reverse proxy injects the right key into outbound model requests and scopes it to the runtime, so a Claude box cannot obtain the OpenAI credential. The box gets a copy of the agent's HOME state with credential-shaped entries removed.
The human can observe the page without moving the browser back onto the host:
$ h5i box view fix-auth # the box's page, on a loopback-only forward
$ h5i box view fix-auth --term # draw it in this terminal instead
$ h5i ui # the whole fleet, read-only, every route a GETFrames cross outward through the viewer. The browser profile, page execution, and network identity do not. If a human takes control, the control transfer is recorded and stale page handles are invalidated before the agent resumes.
4. Review the whole run, not only the diff
$ h5i box diff fix-auth # against the pinned base
$ h5i box export fix-auth --out ./review
wrote ./review/patch.diff, ./review/report.md, ./review/receipt.json
$ $EDITOR ./review/report.md # read this first
$ git apply --3way ./review/patch.diffThe patch answers what changed. It does not answer which tests ran, what the browser reached, what the boundary refused, or whether a human changed page state during the run. report.md brings those records together without flattening their sources.
Start with denied egress and unavailable evidence. Then read commands with their lanes and exit codes, browser requests and errors, control handovers, and finally the agent's proposal. The proposal comes last because it is testimony from the subject of the review, not an outside observation.
Export is an output gate, not another agent command. The box cannot write ./review; h5i writes the validated bundle from outside after the human asks. Reviewers can carry the patch elsewhere with git apply --3way, which is mandatory for detached boxes.
5. Apply only the result you accept
$ h5i box status fix-auth # check base drift and evidence gaps
$ h5i box apply fix-auth # land the reviewed proposalapply is available only when the box was created from the current repository. It is never an automatic final step. If the parent branch moved, status names the drift; you can rebase the box deliberately, export the patch, or decline the work.
This is where keeping the workflow in one boundary pays off. The reviewer is not reconciling an uncontained browser history with a sandboxed shell and an agent-authored summary. The code, browser behavior, request decisions, and executions belong to one named run, and the parent repository changes only after that run has been examined.
Cleaning up
$ h5i box ls # every box on this clone
$ h5i box status fix-auth # policy enforced, evidence, base drift
$ h5i box rebase fix-auth # re-pin onto the parent's current tip
$ h5i box abort fix-auth # stop, preserving it for forensics
$ h5i box rm fix-auth
$ h5i box gc # reclaim finished workspacesabort stops the box but keeps its evidence. rm deletes it. Keeping those actions separate matters most when a run has done something surprising.
Making it cheap enough to do constantly
A sandbox saved for obviously dangerous jobs will miss ordinary dependency installs, where supply-chain surprises often arrive. The boundary needs to be cheap enough for routine work.
The lighter tiers start quickly. Dependency caches are shared carefully: each project and package ecosystem gets a cache keyed by its lockfile digest. Agent boxes mount it read-only. Only a box with no agent inside may update it, so two agents never meet through writable cache state.
$ h5i box cache refresh npm
$ h5i box cache ls # which are stale, and therefore unusedA test you can apply to any agent sandbox
You can ask the same questions of any tool that claims to contain an agent:
- Where do package install scripts execute, and under which home directory?
- Does the browser run inside the same boundary as the checkout and dev server?
- When the browser opens localhost, whose loopback does it reach?
- Who wrote the record of what ran: the thing being reviewed, or something outside it?
- Is a refused action recorded, or does it simply not appear?
- Can the agent write the accepted result directly, or does a person carry it across?
If one of those answers points outside the boundary, part of the job is still running uncontained.
What this does not claim
Containment can stop the agent from touching your host. It cannot stop private source from appearing in a model request that policy already allows. If the source must not leave, use a self-hosted model or disable model egress.
Every tier below microvm shares the host kernel. Those tiers are meant for runaway agents and careless dependency code, not a targeted kernel exploit. The microVM tier moves the boundary to a hypervisor.
A receipt is protected from the box, but the host owner can still alter it. It answers a narrow question: could the agent have written this record? It does not prove that the person presenting the receipt left it untouched.
Sources and further reading
- Why sandbox the entire AI agent workload, for the threat model behind the workflow.
- Review AI-generated code with execution evidence, for what a diff, receipt, and agent summary can each establish.
- The first-box guide, for running this workflow once on a real repository.
- Watch the browser, for dev-server loopback and human control transfer.
- The manual, for every flag named above.
Questions that come up
Does --in create the box?
Can an agent already inside the box use --in?
What is the difference between export and apply?
Why sandbox the entire AI agent workload
See why dependencies, tools, tests, servers, and browsers need one boundary.
Start with one box
Run h5i box probe, create a box, and inspect what the host actually enforced.