A permission prompt asks before a risky tool call; a sandbox means the process cannot reach the network or filesystem outside a boundary no matter what it decides to do. The difference between the two, what a real sandbox has to restrict, and a sane default posture for running an agent next to a real repository and real credentials.
three different things people call "safe"
Ask three people what makes it safe to run a coding agent against a real repository and you get three different mechanisms, often used interchangeably even though they stop almost nothing in common. In order of how much they actually restrict the agent:
Only the last two are security boundaries in the sense a security engineer would use the term: a restriction that holds regardless of what the restricted thing decides to do. The first is not, and the gap between "asks first" and "cannot" is the subject of this post.
1. the permission prompt
Most coding agents, run locally, ship a permission system: before writing outside the working directory, running a shell command, or making an outbound network call, the agent stops and asks. You approve or deny, sometimes with a remembered "always allow" for a given tool or path.
This is genuinely useful. It catches the mundane case well: an agent about to rm -rf a directory you didn't expect it to touch, or push to a branch you didn't ask it to push to. A prompt in front of an obviously wrong action, read by a human paying attention, stops that action.
What it actually is, mechanically
The prompt is a check inside the agent's own process. The code that decides "this call needs approval" runs with the same privileges as the code that would execute the call. Nothing separates the part of the program that asks permission from the part of the program that would act without asking. It is, structurally, the same as a function that calls confirm() before doing something: a courtesy the calling code extends to itself.
why a permission prompt is not a boundary
Three ways that courtesy fails to hold, none of them exotic:
- The model does something the prompt logic didn't anticipate. Permission systems enumerate risky tool calls. A capability the list-writer didn't think to gate through it. If the agent can reach the network through any code path the permission layer doesn't intercept, that path is unrestricted by definition.
- Prompt injection changes what the agent decides to do. This is a well-documented category of failure, not a hypothetical: an agent that reads untrusted content (a fetched web page, a file in the repo, an issue comment) can have instructions embedded in that content that the model treats as legitimate. If those instructions steer the agent toward a tool call it would normally ask about, and the injected content also contains text aimed at getting a human to click approve, the prompt is not protecting against a compromised decision-maker. It is being asked by the compromised decision-maker.
- Approval fatigue makes the human part of the system unreliable. A prompt that fires on every file write gets "always allow"'d within the hour. This is not a flaw in any particular person; it is what happens when a security control has a cost paid on every single action. A control whose cost scales with usage gets worn down by usage.
None of this makes permission prompts worthless. It makes them a UX feature layered on top of trust in the agent process, not a substitute for restricting what that process can do. The question worth asking about any agent setup is not "does it ask before risky actions" but "what happens if it does the risky action without asking, because it was wrong, tricked, or buggy." A sandbox is the answer to that second question.
2. the sandbox
A sandbox restricts what a process is capable of, enforced by something outside the process: the kernel, a filesystem namespace, a network policy. The process does not get a vote. If it is not permitted to open a socket to a given host, attempting to do so fails the same way it fails for any other unprivileged process on the machine, whether the attempt came from a well-reasoned plan, a hallucinated one, or injected instructions makes no difference to the kernel.
This is the mechanical difference that matters: a permission prompt is inside the trust boundary of the agent. A sandbox is the trust boundary. Put differently, a permission prompt answers "should I," enforced by the thing being asked. A sandbox answers "can I," enforced by something that does not care what the process wants.
what a sandbox actually has to restrict
A sandbox that only restricts one of these is a sandbox with a hole in it. All three need to be true at once for the boundary to mean anything against a coding agent specifically.
Filesystem scope
The agent should be able to read and write the project directory it is meant to be working in, and nothing else by default. Not the home directory, not sibling repos, not ~/.ssh or ~/.aws. The common failure mode here is not malice, it is a shell command the agent runs that happens to resolve a relative path outside the project root, or a cp that globs wider than intended. A filesystem boundary makes that a failed syscall instead of a mistake that silently succeeds.
Network egress
The meaningful control is an allowlist of hosts the sandbox can reach, not a blanket "internet access on/off" toggle and not a prompt that fires per request. A process that has been tricked into exfiltrating a file does not stop to ask permission for the request that exfiltrates it; egress restriction has to be enforced structurally, at the network layer, for it to matter in exactly the scenario it exists for.
# illustrative shape of a scoped egress policy, not a specific product's syntax
allow:
- registry.npmjs.org
- github.com
- api.anthropic.com
deny: "*"Credential access
This is the one that turns a contained mistake into an incident. An agent that can read your standing SSH private key or your long-lived cloud credentials can act as you anywhere those credentials are valid, which is usually far outside the project directory the sandbox otherwise scoped it to. The fix is not "don't give the agent credentials," it is scoping what it gets: a short-lived, narrowly-permissioned token injected into the sandbox for the one operation that needs it (a git push to one repo, a deploy to one staging environment), rather than the same broad credentials sitting in your home directory that every other process on your machine can also read.
3. the container or VM
Filesystem scope, network egress and credential scoping are policies. Something still has to enforce them, and that enforcement lives at the OS level: namespaces and cgroups for a container, a hypervisor boundary for a VM. This is the layer that makes "the process cannot reach outside its boundary" true rather than merely configured.
For most local development, a container with an actually-restricted mount and an actually-restricted egress policy is a reasonable default. The step up to a VM buys a boundary that survives a kernel-level compromise of the sandboxed process, which is worth paying for once real production credentials or genuinely untrusted, unreviewed code are in the picture, and not necessary for most everyday repo work.
a default posture, not a certified one
This is a rule of thumb for running an agent against a real repository with real credentials somewhere on the same machine, not a compliance checklist:
- Run the agent in a container scoped to the project directory. Not the home directory mounted read-write, the project directory. Everything else on the machine should be invisible to it by default.
- Set an egress allowlist, not a prompt. Package registries, the model API, the specific git remote. Deny the rest at the network layer.
- Inject scoped, short-lived credentials for the one action that needs them, then let them expire. A push token good for one repo and one hour is a smaller blast radius than a standing personal access token with account-wide scope sitting in an environment variable.
- Keep the permission prompt. It is still the right first line for the actions a sandbox is not built to catch, like "this diff touches a payments file, are you sure." Keep it for what it is good at and stop relying on it for what it cannot do.
- Treat anything the agent fetched as untrusted input to itself. Web pages, issue text, README files in a cloned dependency. None of that content should be able to widen what the sandbox permits, whatever it says.
None of this requires exotic infrastructure. It requires treating the agent process the way you would treat any other automated process with write access to your code and reach to your network: scoped by default, not trusted by default and merely asked to behave.