AI CodingAI AgentSecurity & Supply Chain

Why Do Coding Agents Need a Sandbox?

For developers who use Cursor or Claude Code regularly, the Sandbox may already be an everyday interface. When an agent is about to run a command, the system asks whether to place it in a Sandbox; we glance at the command, click approve, and keep working. The action is familiar enough that the question behind it tends to slip by: Why do coding agents need a Sandbox?

A few common confusions cluster around this question:

These questions seem scattered, but they all overlook the same distinction: approval can only decide whether the command at hand may start; the Sandbox must also handle what follows after it starts. A program will go on to read configuration, load dependencies, spawn child processes, and may ultimately modify external systems using valid credentials.

Let’s trace through a complete bug-fix task. The agent will clone a repo, run npm install, modify files, execute npm test, and finally push the branch back to GitHub. This process explains why a Sandbox cannot be reduced to a command blocklist, and clarifies what operating-system isolation, external permissions, and independent workspaces each solve.

Approval Only Decides Whether to Launch; the Sandbox Handles What Follows

Start with an ordinary npm install. What the user approves are those two words, but once the package manager begins, it will also read package.json and lockfiles, download dependencies, and run the lifecycle scripts declared by each package. Those scripts, in turn, may start Node.js, Python, compilers, or other shell programs. None of these subsequent programs appear in the approval dialog, because they only emerge after the package manager reads the project configuration.

The same holds for npm test. Which tests actually run, which plugins are loaded, and which temporary files are created are not written into that command. The answer is scattered across the repository code, test configuration, and dependencies, and may also be influenced by environment variables and local machine state. Reviewing the launch command can determine whether it aligns with the current task, but it cannot deduce the full execution path from those two words alone.

Faced with this uncertainty, three common alternative approaches come up. Evaluating whether they suffice can be reduced to two questions: can the approach preserve the coding agent’s ability to use the existing toolchain, and can it constrain the consequences of unknown code?

  1. Maintaining a command blocklist. The system can block high-risk commands like rm and sudo, but this only covers known entry points. Python’s file API, Node.js programs, and build scripts can all delete the same file. To enumerate every equivalent path by name, the system would ultimately need to understand the consequences that every language, every tool, and every piece of project code can produce.

  2. Requiring human approval for every step. Approval suits a small number of actions with clear intent and meaningful impact; it does not suit every file read, temporary write, and child-process invocation. A single test run generates a flood of low-level operations. If each of them stopped and waited for human confirmation, the agent could not complete a diagnosis in a continuous fashion; the human, in turn, would struggle to judge whether a particular file access by an unfamiliar test plugin was actually necessary.

  3. Removing Bash and exposing only dedicated tools. For closed tasks that only need to read issues and generate draft replies, this may be enough. The set of operations a coding agent faces, however, is not fixed: every repo brings its own compiler, test framework, scripts, and debugging tools. Replacing all of that with purpose-built APIs would mean the platform continuously re-wrapping the command-line ecosystem.

Each of the three approaches solves part of the problem. A blocklist preserves tooling capability but cannot cover equivalent operations; step-by-step approval preserves human judgment but cannot keep up with the invocation frequency of a development toolchain; dedicated tools can narrow permissions but struggle to accommodate open-ended development tasks. The Sandbox takes a different trade-off: it preserves the ability to execute code, and enforces file and network restrictions underneath the process.

Anthropic’s engineering writeup on using code execution with MCP illustrates the benefit of keeping a general interface: the agent can discover and invoke tools through code, and can leave intermediate results on the file system rather than sending every tool and every result into the model context. Cursor’s terminal documentation, meanwhile, separates two moments: Run Mode decides when a command runs directly, when it asks first, and when it enters a Sandbox; the Sandbox then restricts which files and networks the already-launched command may access.

A Sandbox, therefore, does not need to prove a program correct, nor to enumerate the full execution path in advance. It addresses the access scope after the program starts: even if the package manager later loads a script that never appeared in the approval dialog, that process is still subject to the same resource limits.

This explains why approval cannot replace a Sandbox. But if the agent itself has no intention of attacking the user, why enforce those limits through the operating system? The answer begins with the code that actually participates in the task once the command has started.

The Agent Bears No Malice — Why OS-Level Isolation?

A development task involves more actors than just the agent. Tracing that earlier npm install and npm test, risk enters from three places: the agent’s own judgment, the external content it reads, and the third-party code it launches.

  1. The agent can misjudge. It may misinterpret the directory structure and miscalculate a path when cleaning test caches; it may also write a command whose scope is too broad. File-access restrictions can contain such errors within the task workspace instead of letting them reach other projects or user directories.

  2. External content can alter the agent’s judgment. Issues, code comments, and web pages are the material from which the agent analyzes the problem; they may also carry prompt injection. Once the agent mistakes such content for the user’s instruction, it will still use its normal tools and credentials — only the objective has drifted from the original task.

  3. Development tools execute code the model did not write. npm install runs dependency scripts, the build process loads plugins, npm test executes code from the repo and third-party packages. These programs are not subject to model alignment. Even when the agent itself chose the right command, the toolchain it invokes may still read files irrelevant to the task or establish unnecessary network connections.

All three risks ultimately manifest as file access and network access by a process. OS-level isolation does not need to distinguish whether the error came from the model, the prompt, or a dependency package; it only needs to enforce the same set of rules on Bash and every program it spawns.

According to Claude Code’s security isolation documentation, it uses Seatbelt on macOS and bubblewrap on Linux or WSL2 to restrict file-system and network access of the sandboxed Bash. This description covers Bash and its child processes; it does not warrant assuming that every tool within Claude Code falls inside the same boundary.

The specific policy can, for instance, allow a process to read and write the current task workspace while denying irrelevant directories; it can open the network addresses needed to install dependencies without opening arbitrary outbound connections. These are only choices a policy can express; the actual protection depends on the configuration. The system does not need to decide whether an install script is benign — it only needs to constrain which resources that script can touch.

Dual risks under task execution: errors from the agent itself and unknown code in third-party dependencies both enter the isolated workspace, where OS-level isolation restricts their access to host resources

Up to this point, OS-level isolation is still restraining processes on the local machine. When the local tests pass and the agent pushes the branch back to GitHub, the task crosses that boundary: the host may remain untouched, but a remote write operation with valid credentials could still exceed what the task requires.

VMs Protect the Host, Not Legitimate Permissions

To push the fix branch, the system must allow the agent to reach github.com. OS-level isolation can still protect host files, but the remote request has already left the machine; so long as the request carries valid credentials, it may modify a real code repository.

Between “allow network access” and “allow a GitHub write operation appropriate to the task,” there are at least three distinct problems. They cannot substitute for one another; permissions must be narrowed step by step.

  1. Network policy controls where requests go. The system might allow only github.com, but a domain name cannot tell whether a request is reading an issue, pushing the current branch, modifying repository settings, or calling a deletion endpoint. The same destination carries operations with entirely different consequences.

  2. Credential proxying reduces raw token exposure. If a GitHub token is placed into the Sandbox environment variables, dependency scripts and test code can also read it. Docker Sandboxes’ security design keeps the raw credential on the host side; the proxy attaches authentication only when the host policy permits the request. This reduces the chance of credentials appearing in plaintext inside the Sandbox. This article has not independently verified the security strength of that implementation.

  3. External policy controls what that identity may do. Hiding the token does not mean the request scope is sufficiently narrow. If the host policy allows write operations across the entire repository, the agent could still issue a request that passes the policy yet exceeds what the current fix task requires. No escape is needed here — the problem is simply that the legitimate permission is broader than the task.

A fix task typically needs only to access a specified repository, push the current working branch, and do so within a limited time window; deleting branches or modifying repository settings can wait for separate confirmation. Policies and call logs reside outside the Sandbox, so even if a process inside it is compromised, it cannot directly modify those constraints.

The division of labor across these three layers proceeds from coarse to fine: domain policy controls network destinations, credential proxying controls whether raw secrets enter the execution environment, and external permission policy controls which remote changes a valid identity can produce. They answer whether the agent is permitted to push results to GitHub — but they have not yet addressed where the multiple rounds of modification before committing should live.

The File System Keeps Changes on the Near Side of Committing

An independent workspace holds the modifications that precede a commit. It keeps the agent’s attempts inside a local copy, and lets a code change pass through three stages before it reaches GitHub.

  1. First, modify a private copy. After the repo is cloned into the independent workspace, the agent edits source code locally, installs dependencies, and runs tests. What changes at this stage is the copy, not the shared branch on GitHub. A mistaken edit may cause a test to fail, but writing to a file does not automatically push the change into the main line.

  2. Then turn the modification into a reviewable candidate. Git diff shows the concrete changes, tests verify known behavior, and code review judges whether the modification matches the task. These steps cannot prove the code free of defects, but they expose some errors before sharing.

  3. Finally, submit through a commit or Pull Request. At this point, the local changes leave the workspace and enter the team’s shared codebase. The commit action requires the GitHub permissions discussed in the previous section and may again wait for manual confirmation.

These three stages separate experimentation from submission. If one approach to the fix does not work out, the file changes can be reverted; if the dependency environment becomes polluted, the entire workspace can be discarded. A snapshot can also preserve a local state, letting the task return to an earlier file-and-environment checkpoint. Rollback targets changes that are still inside the workspace.

The same file system serves a second purpose. Source code, build logs, and test reports are often far larger than what a model can read in a single pass. Manus’s writeup on context engineering notes that agents can leave such material on the file system, keeping only paths in context, and re-read it as needed. The file system thus records the result of modification, and also lets the agent re-examine the scene at a later step.

That said, the file system can only restore state that lives in the file system. Source code already pushed out cannot be reclaimed through snapshot recovery, emails already sent will not disappear, and remote resources will not auto-rebuild. The local workspace enables code changes to be inspected, discarded, and retried; external actions still need their permissions constrained before they occur.

Staged flow from local workspace validation to external submission: local rollback can only undo file modifications within the workspace, while sensitive external operations and leak risks must be blocked by upstream permission gates

If a task always runs continuously inside the same VM, this is already enough to complete one round of modification and submission. A coding agent’s task, however, frequently pauses to await review, and the underlying VM may end before the task does. When that happens, it is not only files that must be preserved.

The Unit of Sandbox Isolation Is Shifting from Process to Delegation

An agent might run tests, wait for developer review, and return the next day to modify the code based on feedback. The underlying VM may be rebuilt due to timeout or resource reclamation, but the delegation itself has not yet concluded.

For a new instance to continue working, it needs to pick up at least three categories of state.

  1. Workspace state. A file snapshot can bring back source code, dependencies, and test artifacts, but it cannot restore the memory or network connections of the old process.

  2. Task progress. The system also needs to know why the task was paused, which tests have already passed, and which conclusions need re-verification. File content alone may not answer these questions.

  3. Authorization state. Whether a prior manual approval still applies to the next step depends on which action was approved, for which branch, and whether the validity window has ended. This information cannot live only inside the already-destroyed VM.

These three categories of state must be associated with the same task and stored independently of the compute instance. Only then can the system know where to resume after a VM swap, and whether the next external action still needs confirmation. At this point, the time horizon that the Sandbox manages has grown beyond a single process.

Return to the initial cache-fix task. After the Pull Request is submitted, the VM that ran the tests can be destroyed, and the local workspace can be discarded; the changes that entered the shared repository, and the remote requests that have already been issued, will remain. The Sandbox narrows the scope that local execution can reach, the workspace delays the moment at which changes enter the real codebase, and the external policy constrains which remote changes a legitimate identity can produce. Together they shrink the consequence of a mistaken delegation — without proving the code correct on the developer’s behalf, and without deciding for the user which remote changes are acceptable.