How to Sandbox Code Run by AI Agents

Run agent-requested code in an isolated environment with no ambient credentials, a minimal filesystem, default-deny network access, and task-scoped capabilities. Enforce separate CPU, memory, wall-time, process-count, output-size, storage, and network limits. Treat the process boundary and syscall filtering as layers, then test every denied path and cleanup action.
Prerequisites
Before accepting executable code from an agent, define the execution request as data rather than as a shell command assembled by the agent. The request needs to identify the code or approved command, declared inputs, expected output type, required network destinations, and requested capabilities. Keep sandbox creation, policy selection, credential brokering, termination, and result collection in an orchestrator the executed code cannot modify.
You also need an inventory of what the workload genuinely requires. If you do not know whether it needs a compiler, package registry, writable disk, network connection, or service credential, observe the workload in a disposable test environment before granting those things in production. Unknown requirements are not a reason to start with broad access.
Steps
-
Put execution behind an isolation boundary designed for untrusted code
Do not run agent-requested code as another child process of the agent service and call that a sandbox. A process boundary alone does not prevent filesystem reads, network egress, or resource exhaustion. Unless other controls intervene, the child still operates through the host kernel, inherits some execution context, and can consume resources visible to its parent.
Use a disposable container, virtual machine, microVM, or managed execution environment whose isolation and lifecycle are separate from the agent service. The important condition is not the product label; it is whether the boundary lets you independently restrict files, network, credentials, processes, and resources.
A managed option reduces the platform work when you need full Linux execution without maintaining the container control plane yourself. For example, the Cloudflare Sandbox SDK overview says, as updated August 13, 2026, that its sandboxes run in isolated containers and support commands, files, processes, code interpretation, and outbound-traffic handling. The same page places the product on the Workers Paid plan and identifies the next major SDK release as a preview, so check the release line before adopting it. A managed service is the wrong answer when its runtime, regional availability, network controls, or lifecycle cannot express your policy; self-hosting is the wrong answer when your team cannot operate and patch the isolation layer.
Treat the boundary as one layer, not proof that the workload is safe. Record which guarantees come from the runtime and which controls you must add around it.
-
Build a minimal, disposable execution environment
Start from an image containing only the interpreter, compiler, libraries, and utilities required by the approved workload. Run as an unprivileged user, remove unnecessary administration tools, keep the base filesystem read-only where the runtime permits it, and provide a separate writable workspace for the task.
Mount or copy only declared inputs into that workspace. Do not mount the agent service repository, user home directories, host sockets, deployment configuration, or a shared cache merely because they are convenient. A process that can read a mounted path can inspect everything its operating-system permissions allow, including files unrelated to the task. Define the readable and writable paths as part of the policy; the detailed design belongs in bounding agent filesystem access.
A small image and a fresh workspace cost build time and may make dependency installation less convenient. Reusing a large development image starts faster for varied tasks but exposes more programs and a larger filesystem. Reusing a writable workspace can preserve useful build artifacts, but it also mixes state between executions. Use reuse only for an explicitly named session or tenant, and never as an accidental consequence of choosing the same sandbox identifier.
Destroy the writable layer after collecting the allowed result unless persistence is part of the request. If persistence is needed, copy out named artifacts through the orchestrator rather than retaining the whole sandbox.
-
Start with no credentials and broker each capability
The sandbox should start with no credentials and receive only task-scoped inputs and short-lived capabilities. Do not copy the agent service environment into it. Do not bake secrets into the image, place long-lived deployment keys in the workspace, or expose a host credential socket.
Separate task data from authority. A source file is an input; permission to upload the resulting artifact is a capability. Give the source file at startup. Grant upload authority only if and when the execution reaches the upload operation.
Prefer a broker outside the sandbox. The sandbox asks the broker for a defined operation, and the broker checks the task identity, destination, method, object, size, and expiry before performing it. This keeps the underlying credential outside the executed process. If the target system requires the sandbox to authenticate directly, issue the narrowest short-lived credential available, deliver it just before use, and expire or revoke it at task completion. A credential that can list every bucket is not task-scoped when the task only needs to write one object.
This design adds a service hop and requires capability definitions. Directly injecting a broad API key is simpler, but any code in the sandbox can try to read and reuse it. Use the broker for consequential writes, production systems, and credentials that cannot be narrowed. Direct credentials are acceptable only when the target supports a scope and lifetime matching the task and disclosure remains inside the accepted risk. Keep the issuance path consistent with provisioning service credentials for an agent and define where issued credentials may be stored before the first production run.
-
Make network access default-deny
Block outbound connections unless the task contract names a destination and operation. Enforce the rule outside the executed process, because code that controls its own firewall can remove its restrictions. Apply the policy to every route out of the sandbox, including name resolution and indirect access through proxies or mounted services.
An allowlist needs to describe more than a hostname when the operation is sensitive. Bind permission to the required protocol, destination, method, and task where the enforcement layer supports them. Route authenticated traffic through the capability broker so the broker can add authorization after accepting the request. This separates permission to contact a service from possession of its credential.
Unrestricted egress is the wrong answer even for code whose stated job is downloading a dependency: the same path can transmit workspace contents or contact an unintended endpoint. A total network ban is the right default for pure computation, but the wrong final policy for a task that must retrieve a declared package or call a service. In that case, grant the narrow route and account for its bytes and duration. The implementation choices and their failure modes are covered in controlling network egress from agent sandboxes.
-
Set an independent budget for every exhaustible resource
CPU, memory, wall time, process count, output size, filesystem, and network each need an explicit limit because exhausting any one can deny service. A CPU quota does not stop a process from allocating memory; a wall-time limit does not stop it from filling disk first; a byte limit on stdout does not limit outbound traffic.
Define all seven limits in the execution policy and enforce them in the runtime or an external supervisor. Do not ask the model to respect them voluntarily.
Resource What the limit must govern Failure when omitted Required terminal action CPU Compute consumed by the task and its descendants A busy workload occupies shared compute Throttle or terminate the whole task Memory Resident memory across the task boundary Allocation pressure disrupts the worker or neighboring workloads Terminate the task and mark a memory-limit result Wall time Elapsed time from execution start Sleeping, blocked, or slow work holds capacity indefinitely Cancel descendants and close capabilities Process count All children, not only the first process Repeated spawning consumes process slots Reject new children or terminate the task tree Output size Captured standard output, standard error, logs, and returned artifacts Unbounded results consume memory, storage, or response bandwidth Stop capture at the bound and report truncation Filesystem Writable bytes, file count, and permitted paths Generated files fill storage or escape the intended workspace Reject writes, then discard the writable layer Network Allowed destinations, connections, transferred bytes, and connection duration Traffic consumes capacity or becomes a path for extraction Deny the connection or close the route The supplied sources do not establish universal numeric budgets, and there is no single correct value across compilation, data analysis, and small scripts. Measure known-good tasks, choose limits for each workload class, and store concrete values in versioned policy. A timeout named
shortor memory namedsmallis not an enforceable specification.Every limit trades task completion against service protection. Set a separate policy for a compilation job if it legitimately needs more memory than a text transformation; do not enlarge the default for every task. If a task exceeds a limit, return which limit fired rather than silently retrying with more authority. Automatic escalation turns an enforced budget into a suggestion.
Resource limits also address a risk already recognized in the field. The OWASP Top 10 for Large Language Model Applications project page, accessed August 26, 2026, points to its 2026 release published August 4, 2026 and retains historical entries describing resource-heavy operations as a denial-of-service risk, alongside insecure output handling, sensitive-information disclosure, and excessive agency. Those categories are reasons to constrain execution and authority independently rather than relying on prompt instructions.
-
Reduce the kernel interface available to the workload
After the outer boundary and resource controls exist, reduce the system calls the workload can make. Start from the calls used by known-good executions and deny unnecessary operations such as kernel administration, mounting, tracing, or other runtime-specific privileged behavior.
On Linux, seccomp BPF is one available layer. The Linux kernel seccomp documentation, undated and accessed August 26, 2026, says filters inspect the system-call number and arguments to reduce exposed kernel surface. It also states plainly that syscall filtering is not a sandbox and that logical behavior and information flow need other hardening controls. The documentation warns that filters must check the architecture value and that allowing
ptracein a seccomp-based sandbox requires extreme care.This layer reduces what code can ask the kernel to do, but it does not decide which project file may be read, which host may receive traffic, or how much CPU may be consumed. Those are separate controls from the earlier steps. A permissive filter is appropriate during observation of a new workload, provided the environment is disposable and contains no credentials or sensitive data. It is the wrong production policy once the required call set is known. An overly narrow filter is also wrong when it breaks ordinary runtime behavior without producing a diagnosable denial.
Test the filter on every supported architecture and runtime version. Treat a required new syscall as a policy change requiring review, not as a reason to disable filtering globally.
-
Bind isolation, policy, and audit records to one task identity
Create a unique execution identity that connects the incoming request, selected policy version, sandbox instance, issued capabilities, output, limit events, and cleanup result. The identity must not be a shared human credential. Keep the mapping in the orchestrator, outside the sandbox, following the same principle as giving an agent its own identity and audit trail.
Log policy decisions rather than unrestricted contents. Record that a destination was denied, a memory limit fired, or an upload capability was used; do not defeat isolation by copying every input, secret, and output into a central log. Bound log volume under the same output policy as other results.
Per-task instances make cleanup and attribution simpler but add startup work. A session-scoped instance can retain interpreter state and dependencies, but its files and processes become part of the next execution’s starting condition. Use session reuse only when that continuity is required, keep it within one security principal, reapply limits to every execution, and destroy it at the end of the session.
-
Terminate the entire task and clean up in a fixed order
Completion, timeout, cancellation, and policy denial must all enter the same cleanup path. First stop accepting new broker requests. Then revoke or expire task capabilities, terminate the process tree, stop output capture, collect only approved artifacts and bounded diagnostics, and destroy the writable environment.
Do not kill only the original PID. The task may have children or background processes, so termination must target the isolation unit. Do not copy the whole workspace out for debugging after a policy failure; doing so bypasses the artifact allowlist and may retain material the sandbox was meant to contain.
Return a structured result distinguishing successful completion, program error, policy denial, resource-limit termination, platform failure, and user cancellation. Include the triggered limit and whether output was truncated. This makes retries a caller decision. Retry a platform failure under the same policy if the operation is safe to repeat; do not retry a policy denial with broader access automatically.
-
Test denial paths before allowing production inputs
Build tests that intentionally attempt each prohibited action: read outside the workspace, write to a read-only path, contact an unlisted destination, inspect ambient credentials, spawn beyond the process limit, fill memory, consume CPU, wait past wall time, flood output, fill writable storage, and exceed the network budget.
For each attempt, assert three things: the operation was denied at the expected layer, the task ended with the correct structured result, and subsequent work still received capacity. Also verify that task-scoped credentials are unusable after cleanup, artifacts not named in the request were not retained, and a new sandbox cannot see the prior workspace.
Run known-good workloads alongside the negative cases. A sandbox that blocks attacks by blocking every real task is not ready. Keep the tests tied to the versioned policy and rerun them when the base image, kernel, runtime, dependency set, or managed sandbox release changes.
Expected result
Done means agent-requested code runs in a disposable isolation unit with no ambient credentials; only declared files, destinations, and short-lived capabilities are available; CPU, memory, wall time, process count, output size, filesystem use, and network use each have enforced limits; syscall filtering narrows the kernel surface without being treated as the sandbox; every execution has a task identity and bounded result; and success, denial, timeout, cancellation, or failure all revoke access and remove writable state through the same tested cleanup path.
Sources
- Cloudflare Sandbox SDK overviewdevelopers.cloudflare.com
- OWASP Top 10 for Large Language Model Applications project pageowasp.org
- Linux kernel seccomp documentationdocs.kernel.org
See also
Persist delayed work, define missed-run behavior, and make scheduled agent handlers reject duplicate delivery.
Billing shape, duration ceilings, warm state, cold starts and concurrency limits compared for agent runtimes — with a per-condition recommendation.
Separate streamed text from committed agent state, route tool events independently, and make disconnects end in an explicit error or resumable turn.
Choose by consumer: schemas for program-read results, free text for people, and a hybrid when both need the same agent output.