Sandboxing agents that run code: isolation, network policy, and what to let back into context
When an agent writes and runs code, the sandbox is the real security boundary. Here is how hosted containers and self-hosted isolate APIs actually work as of November 2026, and the mistakes that leak files, tokens, or untrusted output.
Some links are affiliate links; we may earn a commission at no extra cost to you. As an Amazon Associate I earn from qualifying purchases. Disclosure
A typed tool call is a narrow door. The model asks for create_invoice(amount, customer_id), your code checks the arguments, and the blast radius is whatever that function can do. A code-execution tool is a room. The model writes a script, the runtime runs it, and anything that room can see, write, or reach is in play: files you uploaded, environment variables you exported, a GitHub token that landed in .git/config, and whatever stdout you feed back into the next model turn. Approval gates decide whether a risky call happens at all, which we covered in approval gates for agent tool calls. Sandboxing is what happens after you say yes. This piece is about that boundary as it exists in November 2026: what hosted containers actually isolate, how network policy punches holes in them, and why the result that comes back into context is still untrusted input.
Why a sandbox is not the same as a function call
Most agent harnesses start with functions you wrote. The model cannot invent a new syscall; it can only fill in the schema you published. That is a good default, and writing tool descriptions and schemas is still how you keep those calls reliable. Code execution shows up when the next step is awkward to pre-declare: reshape a CSV, plot a histogram, run a simulation, compile a snippet, or filter a pile of search results before they hit the context window.
The three major model APIs all offer a version of this, and they are not interchangeable. Anthropic's code execution tool runs Bash and file operations in a server-side container that your application never has to host. You add a tool such as code_execution_20250825 (or a later type if you need REPL state and programmatic tool calling), and the API executes the commands and returns the results in the same request. You do not send tool_result blocks yourself, unless Claude also called one of your own client tools in that turn. OpenAI splits the job in two. Code Interpreter is a sandboxed virtual machine for Python, which the model treats as the "python tool." The shell tool is a full terminal, either in an OpenAI-hosted container or in a local runtime you execute yourself, and it is available on the Responses API rather than Chat Completions. Gemini's code execution tool is narrower still: the model can generate and run Python, and the docs are explicit that it cannot use the tool to run another language even if it can write that language.
Those differences matter because they change what "the sandbox" is. Anthropic and OpenAI are offering you a workspace that can persist files across requests if you reuse a container id. Gemini is offering a short-lived Python cell. If you design a multi-step analysis agent against the wrong mental model, you will look for files that were never kept, or you will assume a network that was never there.
What hosted containers actually isolate
Start with the hosted option, because that is what most teams reach for first and because the docs are unusually specific about the walls.
Anthropic's container has no internet access and no outbound network requests. Claude cannot download packages at runtime, so only the pre-installed libraries are available: pandas, numpy, matplotlib, a documented list of file and math packages, and tools such as rg, fd, and sqlite. The box has 5 GiB of RAM, 5 GiB of workspace disk, and one CPU, and file access is limited to the workspace. Each request starts a new container unless you pass back the container.id from an earlier response. After about five minutes of inactivity the container is checkpointed, and you can restore it inside a 30-day window; after that, the old id errors instead of bringing the files back. The expires_at timestamp on the response is a shorter rolling value and does not report that 30-day limit. Generated files are not whatever Claude wrote anywhere: each bash_code_execution call gets a fresh $OUTPUT_DIR, and only files left at the top level of that directory come back as file_id entries you can download through the Files API. A report written to /tmp stays in the container until you reuse it and ask Claude to copy the file over. Container data is retained for up to 30 days, and the docs mark the tool as not eligible for zero data retention.
OpenAI's Code Interpreter is also a fully sandboxed virtual machine, but the lifecycle is much shorter. You pass a container object, either "type": "auto" or an id you created through /v1/containers. Memory tiers are 1g (the default if you omit memory_limit), 4g, 16g, or 64g, and the choice sticks for the life of that container. A container that sits unused for 20 minutes expires. The docs tell you to treat containers as ephemeral, download anything you need while they are active, and not expect to revive an expired one. Python objects in memory die with it. Files the model creates show up as container_file_citation annotations on the next message, with a container_id, file_id, and filename you can turn into a download.
Hosted shell is the same container machinery aimed at a terminal instead of a Python REPL. The current runtime is Debian 12, the working directory is /mnt/data, there is no interactive TTY, and commands do not run with sudo. Python, Node.js, Java, PHP, Ruby, and Go are preinstalled at the versions listed in the docs. You can create a container, attach skills, and point later Responses calls at the same container_id so files and mounted skills survive across turns, until the container expires or you delete it.
Gemini's environment is the tightest of the three on time. The official I/O details cap the code environment at 30 seconds. If the environment returns an error, the model may regenerate the code up to five times. You cannot install your own libraries; you get the documented preinstalled set, and only matplotlib is supported for graph rendering. Starting with Gemini 2.0 Flash, you can upload text and CSV files and get matplotlib graphs back as inline images. That is a different contract from Anthropic's $OUTPUT_DIR or OpenAI's container file APIs: the useful artifact often arrives already inlined into the model response, which is convenient and also means it is already in context.
None of this is free in the cost sense, even when a provider calls the tool itself free. We walked through the line items in hosted tool fees: Anthropic bills code execution on its own by container time with a five-minute minimum, OpenAI bills Code Interpreter and hosted Shell by memory tier and session time, and Gemini's code execution has no separate tool fee beyond tokens. Isolation policy and billing policy collide whenever you leave a large container warm "just in case."
Network policy is the hole that eats the wall
A sandbox with no network is a strong default for data analysis. It is a non-starter for an agent that needs to pip install, clone a repo, or fetch a release page. The moment you open egress, the isolation story changes from "the code cannot leave" to "the code can leave toward the destinations you named."
OpenAI's hosted containers have no outbound network access by default. Turning it on takes two steps: an admin has to configure the organization allow list in the dashboard, and the request has to set a network_policy of type allowlist with an allowed_domains list. The request can only shrink that list. If you include a domain that is not on the org allow list, the request fails. The shell docs are blunt about why this is not a casual toggle. Allowlisting domains introduces prompt-injection-driven data exfiltration: content fetched over the network can carry hidden instructions, and a domain you trusted so the agent could download a package is also a domain that code inside the container can POST a secret to. The same page tells you to review the command and the output, log requested hosts and actual destinations, and remember that once data leaves OpenAI toward a third party, that party's retention rules apply.
Cloudflare's sandbox security guide makes the same point from the other side of the fence, where you operate the isolate yourself. A Container starts with enableInternet: false, so it cannot reach the public internet unless your Worker intercepts the request. Each destination you then allow is "a way out." If you flip internet access on so npm install works, a dependency can send a token to any server. The recommended pattern is to keep internet access off and let an outbound handler in the Worker allow the package registry and block everything else.
Anthropic's hosted code execution does not give you this knob. The network is off, period. If the agent needs fresh pages, you fetch them with web search or web fetch and let Claude filter or transform the results inside the container. That pairing is also how Anthropic makes code execution free on a request, which we noted in the fees piece; the security reason to keep the two tools separate from a client-side shell is different. Anthropic's own docs warn that when you combine server-side code execution with a client-provided Bash tool, Claude is suddenly working in two computers that do not share files or variables. You have to say so in the system prompt, or it will look for a file in the wrong place.
Keep secrets out of the box
Isolation from the host does not isolate the code from itself. Cloudflare's security page puts this in plain language: assume the code uses everything it can reach inside its sandbox, including the files, credentials, and network access your application placed there. Unix file permissions will not save you in a deployed Cloudflare Container, because every process has the same Linux capabilities as root and can switch back to root. The sandbox, not a Linux user, is the smallest unit of trust. Two users who share one sandbox can read each other's files. A snapshot restores those files, token and all.
The concrete failure mode they describe is a coding agent that clones a private repository with a token in the clone URL. Git writes that URL into .git/config. Later, the tests and every dependency they load run with the same access as the agent, and any of that code can read the token and send it wherever the sandbox can reach. Environment variables and command-line arguments fail the same way. The fix is to keep the credential in your Worker and attach it after the request leaves the sandbox, and only on HTTPS.
OpenAI's hosted shell has a related primitive called domain_secrets. You list a domain, a friendly name such as API_KEY, and the secret value. At runtime the model sees a placeholder like $API_KEY instead of the raw credential, and an auth-translation sidecar applies the real value only for approved destinations. Raw values are not supposed to persist on API servers or appear in model-visible context. That is a better pattern than exporting GITHUB_TOKEN=... into the container environment, but it still depends on the allow list being tight. A secret that can be sent to a domain an attacker controls is not a secret for long.
If you are running commands yourself, Anthropic's open Sandbox Runtime is aimed at this local case. It is a research preview built for Claude Code, shipped as @anthropic-ai/sandbox-runtime, and it wraps a process with OS primitives rather than a full container: sandbox-exec and Seatbelt on macOS, bubblewrap and a removed network namespace on Linux, and a dedicated srt-sandbox account plus Windows Filtering Platform rules on Windows. The README's dual-isolation rule is the one to steal even if you never install the CLI. Without filesystem isolation, a compromised process can read SSH keys. Without network isolation, it can ship those keys out. Writes and network access are denied by default. Reads are allowed by default, so you still need an explicit denyRead for **/.env, ~/.ssh, and your secrets directory.
What comes back into context is still untrusted
The sandbox's job is to stop the code from touching your laptop or your production database. It does not automatically make the output safe to feed back to the model, store in your app, or show in a user's browser.
Stdout, stderr, generated files, and HTTP responses from the sandbox are data the untrusted code produced. Cloudflare says they need the same checks as user input before your application stores them, acts on them, or displays them. A web preview is worse, because the page runs in the visitor's browser under whatever hostname served it. If that hostname is your application, the preview can see your cookies and call your APIs. Serve previews on a separate domain that your app does not use.
The model-facing version of the same problem is familiar if you have read our piece on prompt injection in tool results. A CSV the user uploaded, a page the agent fetched, or a package README the sandbox printed can carry instructions. The sandbox may have stopped those instructions from running curl toward an unexpected host, especially if egress is off. It does not stop them from sitting in stdout and becoming the next user turn. Cap what you echo back. OpenAI's shell_call items already carry a max_output_length and a timeout_ms; their local-shell guidance is to honor that limit in the output you return, enforce your own timeout even when the model suggested one, and include partial output on a timeout so the model can recover. Anthropic surfaces oversized bash output as output_file_too_large and a whole-call overrun as execution_time_exceeded, which is a different signal from the 90-second per-cell limit that code_execution_20260521 tells Claude about. Those errors belong in the same retry-and-stop logic we described in handling tool errors.
There is also a context-cost version of "what comes back." A 200-line traceback, a dumped dataframe, or an inlined graph is now tokens you will pay to carry for the rest of the run. Prefer letting the sandbox write a file and returning a short path plus a summary, then fetching the file only when a later step needs it. That is the same progressive-disclosure instinct as Agent Skills, applied to tool results instead of instructions.
When you run the sandbox yourself
Hosted containers are the right default when the work fits their image, their language list, and their network policy. You step off that path when the agent needs your private packages, your VPC, a custom Docker image, or a coding tool that expects a real machine.
OpenAI's shell tool supports that split directly. Set the environment type to local and the model returns shell_call items for you to execute. The docs do not pretend this is safe by default: inspect each command, allowlist what you can, require approval for destructive or sensitive commands, run the approved command in a sandbox with explicit filesystem, network, memory, and time limits, log the activity, and send stdout, stderr, and the outcome back as shell_call_output. Their own sample executor is a timeout wrapper around a subprocess, not a sandbox. The sandbox is the part you still have to add, whether that is Cloudflare, Sandbox Runtime, or another isolate.
Cloudflare's current Sandboxes offering gives you two shapes through a Worker. Containers run an image you provide as a full Linux environment; each instance is a microVM with its own kernel and network, so no other workload shares it. Dynamic Workers load untrusted JavaScript, Python, or WebAssembly as a separate Worker, and globalOutbound: null makes fetch() and connect() throw. The Worker decides which application APIs and data the code receives. That is the same "keep the credential in the trusted process" design, expressed as a platform feature. Name sandboxes from an authenticated identity, not from an untrusted URL slug, and give each user or job its own sandbox. Reusing a name hands the new caller the previous caller's files.
For a local coding agent on a developer laptop, Sandbox Runtime or an equivalent OS sandbox is the difference between "the model can see the repo" and "the model can see the repo, your SSH keys, and your password manager socket." Pair it with the approval gates you already have, rather than treating one as a substitute for the other. A human clicking Allow on rm -rf does not confine the command. A sandbox that cannot write outside the project directory makes that click less catastrophic.
Production LLM systems spend a surprising amount of their complexity on exactly these boundaries: which process is trusted, which filesystem is durable, and which outputs are allowed back into the next prompt. AI Engineering by Chip Huyen is the book we keep pointing people at for that wider operational picture, next to the vendor sandbox docs.
Practical picks
Use a provider's hosted code-execution container when the task is analysis, charting, or transformation of files you are willing to upload, and when default-off networking is a feature rather than a blocker. Match the lifecycle to the product: Gemini's 30-second Python cell is for a quick compute step, OpenAI's 20-minute idle expiry is for a working session you should drain and delete, and Anthropic's checkpointed 30-day container is for work you may resume later, with a retention policy you cannot wish away with zero-data-retention. Use hosted shell when the agent needs more than Python and you are prepared to maintain an organization allow list plus a per-request network_policy. Run your own isolate when the agent has to touch private systems or a custom image, and then follow the dual-isolation rule: lock the filesystem and the network, keep secrets out of the box, and treat every byte that comes back as untrusted. Give each user or job its own sandbox. Download the artifacts you care about onto storage you control, then delete the container instead of hoping idle expiry does it. The sandbox is not a personality trait of the model. It is the room you agreed to let the model work in, and the room is only as small as the files, credentials, and destinations you put inside it.
Sources
Anthropic: Code execution tool, OpenAI: Code Interpreter, OpenAI: Shell, Gemini API: Code execution, Cloudflare: Sandboxes overview, Cloudflare: Sandbox security, Anthropic Sandbox Runtime.