Sandboxing a Local Coding Agent: Autonomy Without the Blast Radius

Written by Jakub Rusinowski · Last updated 2026-08-04 · Hardware figures computed by our VRAM engine

Approval prompts do not scale — after the twentieth, everyone clicks yes without reading. Replace them with structure: run the agent in a container or VM that only mounts one project, work in a throwaway git worktree so every change is revertable, allowlist the commands that may run unattended, and block network egress unless the task needs it. Then autonomy is a configuration choice rather than a gamble.

There are two ways to run a coding agent. You can approve each action, which is safe and quickly unbearable — approval fatigue is real and measurable, and an unread prompt is the same as no prompt. Or you can enable the auto-approve mode every tool ships and hope. Neither is a design.

The third option is to make the blast radius small enough that autonomy is uninteresting: the agent may do whatever it likes inside a box you can throw away. That takes about twenty minutes to set up and it is what separates a local coding agent you can leave running from one you babysit.

Running the model locally helps with confidentiality — your code is not leaving the machine for inference. It does nothing about consequences. A local 27B can rm -rf a directory or push a broken branch exactly as effectively as a frontier model.

The four things that can go wrong

Design against these specifically, not against a vague sense of risk.

1. Ordinary destructive mistakes. By far the most common: a wrong path, an over-eager cleanup, an edit to generated files, a force push. No malice, just a model acting on a misunderstanding with shell access. 2. Reward hacking. Told to make tests pass, it edits the tests. Told to fix the type errors, it adds any. The output looks green and is worthless — the reason loop engineering insists on freezing the test files. 3. Prompt injection through tool output. The one people underestimate. Anything the agent reads — an issue body, a dependency README, a fetched web page, a PR title — can carry instructions aimed at the agent. This has moved from theory to disclosed, high-severity vulnerabilities in agent-driven CI, and a local model is exactly as susceptible. 4. Credential exposure. The agent reads .env to understand configuration, then includes it in a log, a commit, or a request to a tool. Local inference means the secret is not sent to a model provider; it does not mean the secret stays put.

Layer 1: a container that only sees one project

The standard answer, and the reason devcontainers became the default recommendation for agent work: the agent gets a full development environment, and the environment is not your machine.

// .devcontainer/devcontainer.json — agent-friendly, host-safe
{
  "name": "agent-sandbox",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:20",
  "runArgs": ["--cap-drop=ALL", "--security-opt=no-new-privileges"],
  "mounts": [],
  "containerEnv": { "OLLAMA_HOST": "http://host.docker.internal:11434" },
  "postCreateCommand": "pnpm install"
}

Three details do the work. No extra mounts — the workspace folder only, so the rest of your disk does not exist as far as the agent is concerned. Dropped capabilities so a container escape needs more than a misconfiguration. The model stays on the host, reached over host.docker.internal, so the container needs no GPU and you keep one Ollama instance serving everything.

Two rules that matter more than the file:

If containers are awkward for your stack, a lightweight VM gives the same property with a stronger boundary and more overhead. The principle is unchanged: one project visible, nothing else.

Layer 2: a git worktree you can delete

Container isolation protects the machine. Worktrees protect the work — and they make "revert everything" a one-liner, which is what makes an iteration cap enforceable.

# Disposable branch + directory for one agent task
git worktree add ../repo-agent-auth -b agent/auth-fix
cd ../repo-agent-auth
# ... let the agent work here ...

git -C ../repo-agent-auth diff main        # review before anything merges
git worktree remove ../repo-agent-auth     # or just delete it

Why this beats working in your checkout: your main tree stays usable while the agent runs, a bad session costs a directory rather than a git reset you have to get right, and running several agents in parallel stops being a conflict-resolution exercise. Commit after every verified-green step so the loop always has a known-good point to fall back to.

Layer 3: permissions that are structural, not conversational

Inside the box, still decide what runs without asking. The split that works:

Auto-approve — reads and reversible local actions: reading files inside the project, git status/diff/log, running tests, type checks, linters, builds, package installs into the container.

Always ask — anything with a consequence outside the sandbox: git push, publishing, deploys, database writes, deleting files outside the worktree, changing CI or infrastructure config, adding a dependency.

Never allowrm -rf with a variable path, credential files, the Docker socket, disabling the sandbox itself.

Most agents express this as an allowlist and a denylist; use both, and prefer allowlisting commands over allowlisting "everything except." Two supporting habits: write the same rules into AGENTS.md so the model knows the boundary rather than discovering it by denial, and make sensitive files unreadable in the container rather than relying on a rule — a rule the model cannot violate is worth more than a rule it is asked to respect.

Layer 4: network egress

The cheapest control with the highest payoff, and the one usually skipped. Most coding tasks need no internet at all — the model is local, dependencies are installed, tests run offline. Turning egress off converts the worst prompt-injection outcomes (exfiltrating source, calling out to an attacker's host) into failed connections.

# Run a task container with no network at all
docker run --rm -it --network none -v "$PWD":/work -w /work agent-sandbox

# Or allow only the package registry your build needs
docker network create --internal offline

When a task genuinely needs the network — fetching a package, browsing docs — grant it for that task, in that container, and take it away afterwards. If a browser-automation MCP server is connected, note that it is a network path by definition and treat that session as untrusted; see MCP with local agents.

A twenty-minute setup that covers most of it

The full stack is worth it for unattended loops. If you want the 80% today:

1. git worktree add ../repo-agent -b agent/task — work is disposable. 2. Open that worktree in a devcontainer with no extra mounts and no Docker socket. 3. In AGENTS.md: the fast test command, "never modify tests to make them pass", "never touch .env, migrations, or CI config". 4. In your agent's settings: auto-approve reads/tests/builds; always ask for push, deploy, and dependency changes. 5. Run the container with --network none unless the task needs egress. 6. Cap the loop, and review the diff before it leaves the worktree.

That is a setup where "let it run for an hour while I do something else" is a reasonable sentence — which is the entire point of a local agent that costs nothing per iteration.

What sandboxing does not fix

Be clear about the residual risk, because a sandbox invites confidence.

Sandboxing buys you the right to stop watching every action. It does not buy you the right to stop reading the result.

Frequently asked questions

Do I need to sandbox a coding agent if the model runs locally?
Yes. Local inference means your code is not sent to a model provider — it says nothing about what the agent does on your machine. A local model with shell access can delete files, force-push, or act on injected instructions found in an issue body exactly as a cloud model would. Isolation protects against consequences; local inference protects against disclosure. They are different problems.
What is the simplest safe setup for an autonomous coding agent?
A disposable git worktree opened inside a devcontainer that mounts only that folder, with the model served from the host over host.docker.internal, network disabled unless the task needs it, and an allowlist that auto-approves reads, tests and builds while always asking for push, deploy and dependency changes. That is roughly twenty minutes of setup and covers most realistic failures.
Is prompt injection a real risk for a local coding agent?
Yes — the model being local changes nothing about it. Any content the agent reads can carry instructions: issue bodies, PR titles, dependency READMEs, fetched web pages. Attacks on agent-driven CI have been disclosed with critical severity ratings. The practical defences are treating all tool output as untrusted, disabling network egress by default, and keeping actions with external consequences behind a human gate.
Should I mount the Docker socket into an agent container?
No. Docker socket access is equivalent to host root — escaping the container becomes a single command. If the agent genuinely needs to manage containers, put a filtering socket proxy in front that blocks dangerous operations, or run that work outside the agent’s reach entirely.
Why use a git worktree instead of just working on a branch?
A worktree gives the agent its own directory as well as its own branch, so your main checkout stays usable while it runs, a bad session is deleted rather than reset, and several agents can work in parallel without colliding. It also makes an iteration cap meaningful: "revert everything" becomes deleting a folder.
How do I stop an agent from editing tests to make them pass?
Do not rely on asking. Put "never modify tests to make them pass" in AGENTS.md so it knows the rule, then enforce it mechanically: add a gate that fails when test files changed (git diff --exit-code -- tests/), and where possible make those paths unwritable in the sandbox. A constraint the model cannot violate beats one it is asked to respect.

Keep going