Coding Agents Need a Sandbox Contract, Not a Warning Label

An autonomous coding agent's blast radius is defined by its sandbox and network egress rules. Specify that contract before you specify the agent.

Industrial robotic arm inside a factory cell, joints and cabling visible in dim light

The warning label is not a control

Most teams deploying a coding agent begin with the same artefact: a system prompt that instructs the model not to delete files, not to exfiltrate secrets, not to install untrusted packages. This is a warning label. It is a request, expressed in natural language, to a stochastic system that may be influenced by any content it reads — a README, a dependency's postinstall script, a stack trace, a fetched issue thread. The instruction competes with every other token in the context window.

The execution sandbox does not compete. It is a boundary enforced by the kernel, the container runtime, and the network layer. If the agent cannot write outside /workspace, no prompt injection will make it write outside /workspace. If egress is denied by default, a generated curl call fails at the socket, not at the model's discretion.

This is why the sandbox contract should be written before the agent is specified. The agent's capabilities, its tool surface, its autonomy level — all of these are downstream of what the environment permits. Specifying the agent first produces a design that the sandbox then has to accommodate, which is the wrong direction of constraint.

Where coding agents actually cause harm

Harm from coding agents is rarely dramatic. It is usually one of three mundane categories, each of which is an environment property rather than a model property.

Filesystem reach beyond the working tree

An agent asked to refactor a module may decide to "clean up" adjacent configuration, rewrite a lockfile, or traverse into a parent directory to resolve an import. If the container mounts the developer's home directory, or the host's source tree, the writable set is effectively unbounded. The failure is not that the model chose badly; it is that the environment offered the choice.

Outbound network calls from generated code or package installs

Dependency resolution is the most common egress path. A package manager will contact registries, and a malicious or typosquatted package can run arbitrary code at install time. Generated code can also make direct calls: telemetry, a fetched schema, an LLM API. Each outbound connection is a potential exfiltration channel for whatever the agent can read. Recent reporting on agent-driven supply-chain incidents illustrates how quickly an install step becomes an attack surface (summary).

Credential access inherited from the developer environment

Agents launched from a developer shell inherit environment variables, cloud credential files, SSH agent sockets, and cached tokens. A long-lived cloud key in the environment is a standing grant of whatever that key can do. The agent does not need to be malicious to misuse it; it needs only to run a command that reads the environment and sends it somewhere, which is a normal thing for build tooling to do.

These three categories are not model failures. They are configuration failures, and they are fixable before a single token is generated.

Specifying the contract

The sandbox contract has three clauses: what can be written, what can be read, and what can leave.

Define the writable set, the readable set, and deny everything else

Start from denial and enumerate grants. A workable default for a coding task looks like this:

writable: /workspace/repo/**
readable: /workspace/repo/**, /usr/lib/**, /usr/share/**
denied:   everything else

The repository is the unit of work. Anything outside it is either a system dependency, which should be read-only, or a host resource, which should not be visible at all. This is a mount policy, not a prompt instruction, and it is enforced by the container runtime.

The trade-off is real: a narrow writable set breaks workflows that legitimately need to touch a parent directory, a sibling repository, or a global cache. Teams that need those workflows must widen the contract deliberately and document why, rather than inheriting a wide contract by accident. Widening is a reviewable event; an accidentally wide mount is not.

Deny egress by default and allowlist registries and endpoints

Network policy should mirror the filesystem policy. The default is no outbound connection. The allowlist is explicit and small: the package registry the project actually uses, the model provider's endpoint if the agent calls one, and any internal service the task requires.

egress: deny
allow:  registry.npmjs.org
allow:  api.internal.example

This constrains the install path and the generated-code path simultaneously. It also makes the agent's network behaviour observable: any connection attempt outside the allowlist is a signal, not noise.

The honest trade-off is operational friction. Legitimate builds sometimes need an endpoint that was not anticipated, and the failure mode is a blocked request that looks like a transient error. The allowlist must be treated as configuration under version control, reviewed like code, and updated when a task legitimately requires it — not loosened to allow: * because one build failed.

Run with ephemeral credentials scoped to the task

Credentials should be minted for the task and expire with it. A scoped, short-lived token issued at sandbox start and revoked at teardown limits the value of any exfiltration. The developer's own long-lived credentials should never enter the sandbox; the agent should not be able to act as the developer beyond the task's boundary.

This clause is often the hardest to implement because it requires an identity system that can issue narrow, short-lived grants. Where that system does not exist, the fallback is to run the agent with no credentials at all and route any privileged operation through a separate, human-approved step.

Detection and response

A contract defines the boundary. Detection tells you when something is pressing against it, and response determines what happens next.

Treat anomalous syscall patterns as the primary signal

Prompt inspection is unreliable. The same instruction can be benign in one context and part of an injection in another, and the model's own narration of its intent is not evidence of its behaviour. The reliable signal is what the process actually does: unexpected open calls outside the readable set, connect attempts to non-allowlisted addresses, execve of shells or interpreters not in the task's profile, unusual file-descriptor churn.

These are observable at the runtime layer without inspecting the model's reasoning. A syscall-level monitor can alert on deviation from a task's baseline profile, and it does not depend on the agent telling the truth about what it is doing.

Log the diff and the command history together

Review after the fact requires both artefacts in one place. A diff without the commands that produced it is hard to attribute; a command history without the resulting diff is hard to evaluate. Storing them together — command, timestamp, exit status, resulting diff — makes post-hoc review possible and makes it possible to reconstruct what an agent did when a test fails days later.

This is a logging concern, not a model concern, and it should be specified in the contract alongside the mount and egress rules.

Require human approval for irreversible operations outside the sandbox

Some operations cannot be undone by reverting the working tree: pushing to a remote, publishing a package, deleting a cloud resource, sending an email, merging a pull request. These should be gated. The gate is not a prompt instruction to "ask before pushing"; it is a policy that removes the credential or the network route required for the operation until a human grants it for a specific action.

Recent discussion of agent autonomy in production settings converges on the same point: the boundary, not the instruction, is what makes autonomy tractable (summary).

Order of specification

The practical consequence is a change in the order of work. Before specifying the agent's tools, its memory, its planning loop, or its autonomy level, specify the environment it runs in: the writable set, the readable set, the egress allowlist, the credential scope, the syscall monitor, the combined diff-and-command log, and the approval gate for irreversible operations outside the sandbox.

The agent is then designed against a known boundary. Its capabilities are the intersection of what the model can do and what the sandbox permits, which is a smaller and more honest surface than a warning label in a system prompt.

The trade-off is that a tight contract will occasionally block a legitimate task, and the team will have to widen it deliberately. That is the point. A boundary that is never tested is not a boundary; a boundary that is widened through review is a boundary that is understood.


Cover photo: Freek Wolsink / Pexels.

ShareXLinkedInPermalink6 min read

Related reading

Questions this answers

Why specify the sandbox before the agent?

The sandbox defines the agent's blast radius. Specifying the agent first produces a design the sandbox must accommodate, which inverts the constraint. Specifying the sandbox first gives the agent a known boundary to be designed against.

Is prompt-level instruction not sufficient for safety?

Prompt instructions are requests expressed in natural language and compete with all other context, including content the agent reads. Kernel, container, and network policy do not compete; they are enforced regardless of what the model decides.

What should the default network policy be?

Deny egress by default, then allowlist specific registries and endpoints under version control. This constrains both package installs and generated code, and makes any non-allowlisted connection attempt a clear signal.

Why are syscall patterns a better signal than prompt inspection?

Prompt inspection depends on interpreting intent, which is unreliable and context-dependent. Syscall patterns are observable behaviour at the runtime layer and do not depend on the agent accurately describing what it is doing.

What operations should require human approval?

Operations that are irreversible outside the sandbox: pushing to a remote, publishing a package, deleting cloud resources, sending messages, merging pull requests. These should be gated by removing the credential or network route until a human grants it for a specific action.