Persistent VMs are now available. Learn more
Beam

Sandbox for AI-Generated Code: Running Agent Code Safely

TutorialsEngineering
Eli Mernit8 min read
Sandbox for AI-Generated Code: Running Agent Code Safely

To run code an AI agent writes, start a fresh sandbox for each task, block or allowlist its outbound network, keep real credentials out of it, cap its runtime, and destroy it when the task ends. The sandbox is the boundary. If the model writes something harmful, or a prompt injection talks it into something, the damage stays inside a machine you were going to throw away.

python
from beam import Image, Sandbox, SandboxProcessError


def run_agent_code(code: str, timeout: int = 30) -> dict:
    sb = Sandbox(
        cpu=1,
        memory="1Gi",
        image=Image(python_version="python3.11").add_python_packages(["pandas"]),
        block_network=True,     # no outbound connections at all
        keep_warm_seconds=120,  # hard TTL for the whole sandbox
    ).create()
    try:
        proc = sb.process.run_code(code, blocking=False)
        try:
            exit_code = proc.wait(timeout=timeout)
        except SandboxProcessError:
            proc.kill()  # wait() stops waiting but doesn't stop the process
            return {"exit_code": None, "error": f"timed out after {timeout}s"}
        return {
            "exit_code": exit_code,
            "stdout": proc.stdout.read(),
            "stderr": proc.stderr.read(),
        }
    finally:
        sb.terminate()

Hand run_agent_code to your agent as a tool. Each call gets a new sandbox with no network, a two-minute ceiling on its life, and a 30-second limit on the code itself, and the sandbox is gone when the call returns.

What running AI-generated code safely involves

Running AI-generated code safely means assuming the code is hostile, even when the model means well. You have four things to protect: the host the code runs on, the secrets and networks it could reach, your compute budget, and the next task's data.

Each one maps to a control:

RiskWhat it looks likeControl
Host escapeCode exploits the kernel or runtime to reach the machine underneathStrong isolation: a user-space kernel or a microVM
ExfiltrationA prompt injection tells the agent to post your files or keys to an outside serverEgress blocked or allowlisted; no real secrets inside
Resource abuseAn infinite loop, a fork bomb, or a crypto minerCPU and memory limits, process timeouts, a sandbox TTL
State leaksFiles from one user's task show up in another'sOne sandbox per task, terminated afterward

exec() in your app server covers none of these. A plain Docker container on a shared host covers some of them, which is why most teams that start there move to a managed sandbox. The Docker sandbox alternatives post covers that move in more detail.

How to run agent-generated code in a sandbox

The pattern is the same on any platform: create, configure, execute with a limit, collect results, terminate. On Beam each of those is one SDK call, and the sandbox docs cover the full API.

Lock down outbound network access

Block the network unless the task needs it. Most data analysis, math and test runs don't, and with block_network=True the sandbox can't open outbound connections, except to the approved hosts of any brokered secrets you bind.

When the code does need to reach something, such as a package index or one API, use an allowlist instead. Set one or the other, since Beam's docs say not to combine an explicit allow_list with block_network=True:

python
sb = Sandbox(
    allow_list=["pypi.org", "*.pythonhosted.org", "api.github.com"],
).create()

Beam allowlists take up to 64 entries: hostnames, wildcard subdomains, bare IPs or CIDRs. Hostname rules match on TLS SNI for port 443 and the HTTP Host header for port 80. The proxy rejects private, loopback and link-local addresses even if you list them, so generated code can't probe your internal network or the cloud metadata endpoint. You can tighten the rules on a running sandbox with sb.update_network_permissions(block_network=True), for example after a setup step that installs packages.

Keep real credentials out of the sandbox

Never pass a real API key into the sandbox through env or a file. Anything the code can read, a prompt injection can tell it to print or send.

Beam's brokered secrets solve the case where the code must call an authenticated API. You create the secret with the hosts it's allowed to reach:

sh
beam secret create OPENAI_API_KEY "$OPENAI_API_KEY" --host api.openai.com

Then bind it to the sandbox:

python
sb = Sandbox(
    allow_list=["api.openai.com"],
    secrets=["OPENAI_API_KEY"],
).create()

Inside the sandbox, OPENAI_API_KEY holds a placeholder. The key itself is substituted only in HTTPS headers on requests to api.openai.com. If the code prints the variable, or sends it to any other host, all it leaks is the placeholder. Sandboxes also don't inherit your Beam workspace token, so generated code can't use your account to start more machines.

Cap time, CPU and memory

Set limits at three levels. The sandbox's cpu and memory bound what a runaway process can use. keep_warm_seconds is a hard TTL for the whole sandbox (the default is 600 seconds), and it counts down whether or not commands are running. proc.wait(timeout=...) bounds how long you wait on one piece of code.

That last one has a catch: a wait timeout raises SandboxProcessError but leaves the process running. Call proc.kill() in the handler, as the example at the top does, or the loop keeps burning CPU until the TTL ends the sandbox.

Move files in and out deliberately

Agents often need to analyze an upload or return a chart. Write inputs in and read outputs back through the filesystem API instead of mounting anything broad:

python
sb.fs.upload_file("report.csv", "/workspace/report.csv")
sb.process.run_code(code, cwd="/workspace")
sb.fs.download_file("/workspace/chart.png", "chart.png")

The sandbox filesystem is temporary. If you mount a Volume for a model cache or dataset, assume the generated code can read and change everything on it, and mount only data it's allowed to touch.

Use one sandbox per task and terminate it

Create a new sandbox for each task or each user session, and call sb.terminate() in a finally block. Reusing a sandbox across users is how one person's files end up in another's output. Sandboxes boot in seconds, so per-task creation costs little latency.

If setup is slow (installing packages, cloning a repo), build it into the Image or take a snapshot once and start each task from it. The snapshot docs cover both filesystem images and memory snapshots.

Containers, gVisor or microVMs for untrusted code

For code nobody has reviewed, you want more than a standard container. A standard container shares the host kernel, so one kernel exploit reaches the machine. gVisor puts a user-space kernel between the code and the host, and a microVM gives the code its own guest kernel behind a hardware virtualization boundary.

Beam sandboxes run in a container runtime by default, and Sandbox(use_vm=True) runs one in a microVM instead. Beta9, the open-source engine behind Beam, ships runc, gVisor and microVM runtimes, and its default worker configuration uses gVisor. MicroVM sandboxes are CPU-only for now, so GPU work runs in the default runtime.

Which to pick depends on who writes the prompts. For an internal agent that writes analysis code for your own team, the default runtime with the network blocked is a reasonable baseline. For a product where anyone on the internet can steer the agent, use use_vm=True. The microVM explainer covers how the isolation models differ.

Defending against prompt injection and exfiltration

Prompt injection is the main reason to sandbox code your own agent writes. A web page, an email or a file the agent reads can carry instructions, and the model may follow them. You can't reliably filter those instructions out, so limit what the code can do if the model obeys them.

Three controls do most of the work:

  1. No secrets worth stealing inside the sandbox. Brokered secrets mean the code only ever sees a placeholder.
  2. Nowhere to send data. With the network blocked or allowlisted, a script that tries to POST your files to an attacker's server fails to connect.
  3. Nothing left behind. A terminated sandbox can't be used for a second, slower attack.

Two gaps to know about. Preview URLs from expose_port() are public, so add authentication in the app before you expose anything that serves data. And an allowlisted host is still a channel: if the code can reach api.github.com with a write-scoped token, it can push data there. Scope tokens to the least the task needs.

What to look for in a sandbox for AI-generated code

Look for isolation stronger than a shared-kernel container, egress controls you can set per sandbox and change at runtime, a way to call APIs without exposing keys, and lifetimes short enough that cleanup is automatic. Then check the practical parts: boot time, SDK quality, and whether it runs the hardware your agent needs.

Here is how Beam handles each one:

  • Per-sandbox egress policy. block_network, a 64-entry allow_list, and update_network_permissions() on a running sandbox.
  • Brokered secrets. Real keys are substituted outside the code's reach, only for hosts you approve.
  • A choice of runtime. The default container runtime, or a microVM with use_vm=True.
  • GPUs on request. Sandbox(gpu="RTX4090") attaches a GPU for code that trains or runs a model. CUDA libraries go in the image.
  • Per-millisecond billing. Sandboxes are billed only while running, at $0.0000375 per physical core per second and $0.0000064 per GiB of RAM per second. A 1-core, 8 GiB sandbox costs $0.319 per hour. See pricing.
  • Python and TypeScript SDKs, plus an open-source engine you can self-host.

If your agent runs tools over MCP rather than raw code, the same controls apply. The MCP server sandbox guide shows that setup.

Beam vs E2B vs Modal vs Daytona for agent code execution

All four give you an isolated sandbox with outbound network controls, so the differences are in the details: how strong the default isolation is, how you write egress rules, and whether real credentials ever enter the sandbox.

BeamE2BModalDaytona
Default isolationContainer runtime (beta9 default: gVisor)Linux VMgVisorLinux container
Stronger optionMicroVM with use_vm=True (CPU only)Already a VMOwn VM with a separate kernel under KVMVM sandboxes
Block all egressblock_network=Trueallow_internet_access=Falseblock_network=TruenetworkBlockAll
AllowlistUp to 64 hostnames, wildcards, IPs or CIDRsallowOut and denyOut lists of IPs, CIDRs or domainsCIDR allowlist, plus a domain allowlist in beta (TLS on 443 only)Up to 10 IPv4 CIDRs, plus up to 100 domains
Change rules while runningupdate_network_permissions()update_network(), which replaces the whole configExperimental APITiers 3 and 4 only
Keys kept out of the sandboxBrokered secrets, substituted in HTTPS headers to approved hostsHeader injection rules in public beta, 10 domains per sandboxNot covered in its networking docsOutbound proxy URL; no injection documented

Where the others are ahead: E2B starts every sandbox as a VM, so you get VM isolation without a flag, and its credential rules can mint workload-identity tokens. Daytona allows more domain entries and documents GPU sandboxes with up to 8 GPUs.

Beam's edge for this use case is combining a large mixed allowlist with brokered secrets that are generally available, so a sandbox can call one API with a real key while the code itself never sees it. For a wider look at the field, see E2B alternatives.

FAQ

Is Docker enough to run AI-generated code?

Not for code from untrusted prompts. A standard Docker container shares the host kernel, and its default network setup allows outbound traffic. It's a reasonable start for code you review, but for agent output that a prompt injection could shape, use a user-space kernel like gVisor or a microVM, with egress blocked.

Can sandboxed code call the OpenAI API or another LLM?

Yes. Allowlist the API host and bind the key as a brokered secret. The code sees a placeholder, and Beam substitutes the real key only in HTTPS headers sent to that host.

How long can a sandbox run?

As long as its TTL allows. keep_warm_seconds defaults to 600 seconds, you can reset it with sb.update_ttl(), and -1 keeps the sandbox running until you terminate it. For a machine that keeps its disk between sessions, use a VM instead.

Do files persist after the sandbox ends?

No. The sandbox filesystem is temporary. Download what you need before you terminate it, mount a Volume for shared data, or snapshot the filesystem to reuse it as an image.

Can AI-generated code run on a GPU?

Yes. Pass gpu="RTX4090" or another supported GPU when you create the sandbox, and include the CUDA libraries in the image. GPU sandboxes use the default runtime, since microVM sandboxes are CPU-only for now.

Are exposed sandbox URLs private?

No. URLs from expose_port() are public HTTPS endpoints, and blocking outbound traffic doesn't change that. Add authentication inside the app you serve.

Run your agent's code in a Beam sandbox

Give your agent a tool that runs its code in a fresh, network-blocked sandbox and returns the output. Create a Beam sandbox and start from the example at the top of this page.

Eli MernitPublished November 4, 2026

Pay as you go
billed by the second

Give your agents their own machines

Sign up, install the CLI, and launch a sandbox, VM, or GPU from your terminal or your coding agent.