On Runtime, this takes one call. Every sandbox is a Firecracker microVM with its own Linux kernel. You can turn off the network when you create the sandbox. Commands time out on their own. The sandbox stops at the time limit you set. A fresh machine for each model answer is practical.
The short answer
TypeScriptimport { Sandbox } from "withruntime";const code = "print(sum(range(10)))"; // what the model wroteawait using sbx = await Sandbox.create({ network: { internet: false }, timeoutSeconds: 300, onLeaseEnd: "stop", maxTotalCostMicros: 50_000,});await sbx.files.write("/workspace/answer.py", code);const run = await sbx.exec(["python3", "answer.py"], { timeoutMs: 30_000 });console.log(run.exitCode, run.timedOut, run.stdout);Pythonfrom withruntime import Sandboxcode = "print(sum(range(10)))" # what the model wrotewith Sandbox.create( network={"internet": False}, timeout_seconds=300, on_lease_end="stop", max_total_cost_micros=50_000,) as sbx: sbx.files.write("/workspace/answer.py", code) run = sbx.exec(["python3", "answer.py"], timeout_ms=30_000) print(run.exit_code, run.timed_out, run.stdout)The sandbox is deleted when the block ends, even after an error.
What each line protects against
| Risk in model-written code | What stops it | Setting |
|---|---|---|
| Escaping to the host | A Firecracker microVM with its own kernel, not a shared kernel | Every sandbox |
| Reaching your network or the cloud | No network card; private and internal addresses always refused | network: { internet: false } |
| Sending your data out | Outbound traffic off, or narrowed to named hosts | internet: false, or allow: [...] |
| An infinite loop | The command returns timedOut: true with its output so far |
timeoutMs (60 s default, 24 h maximum) |
| A sandbox left running | The lease ends and the sandbox stops | timeoutSeconds, onLeaseEnd: "stop" |
| A runaway bill | The sandbox stops when its total cost reaches this limit | maxTotalCostMicros (microdollars) |
| Shell injection in arguments | An array runs the program directly, with no shell | exec(["python3", "answer.py"]) |
| Huge output | At most 64 KiB of stdout and 64 KiB of stderr per result | Automatic; flags say what was dropped |
| Stolen API keys | Secrets reach the sandbox as placeholders it cannot read | Secrets |
Root inside the sandbox cannot change any of these settings. Network rules, CPU, memory and cost are enforced on the host, outside the microVM (security).
Why a container is not enough
A container shares the host's kernel. Code that finds a kernel bug can reach every other container on that machine. A microVM gives the code its own kernel behind a hardware virtualization boundary. A kernel exploit reaches only a disposable machine. AWS developed Firecracker for services such as AWS Lambda and AWS Fargate (Firecracker). The difference is laid out in microVM vs container and Firecracker vs gVisor.
When the code needs the internet
Many answers need a package. Allow only the registries the code needs:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ network: { internet: true, allow: ["pypi.org", "*.pythonhosted.org"] }, timeoutSeconds: 300, onLeaseEnd: "stop",});await sbx.exec("pip install requests", { check: true, timeoutMs: 120_000 });await sbx.network.set({ internet: false }); // off again before the model's code runsA rule applies immediately, including to connections that are already open. See egress control for what each rule covers.
Keep state between answers
A chat assistant that runs code turn after turn needs its variables to persist. The code interpreter keeps them:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ network: { internet: false } });await sbx.interpreter.run("import math\nx = math.pi");const cell = await sbx.interpreter.run("round(x * 2, 3)");console.log(cell.results[0]?.data["text/plain"]); // 6.283Charts come back as PNG files, and data frames come back as tables. A full chat assistant built this way is in code interpreter for chatbots. Python, JavaScript, TypeScript and Bash are in the default image.
Give the agent a key it cannot misuse
The code your agent runs is one risk. The agent's own key is another.
- A daily spending limit per key. A create that would exceed it fails with
spending_limit_reached, and nothing is charged. Only a person can set it. - A read-only key for monitoring and dashboards. It cannot create, run or spend anything.
- Idempotency keys on every write. A retried request never starts a second sandbox.
What it costs
The sandbox above, with 2 vCPUs and 4 GiB, costs $0.03125 an hour while it waits. With both CPUs busy, it costs $0.08 an hour. Runtime bills the CPU the code uses at $0.025 per vCPU-hour, with a floor of a twentieth of a vCPU. Memory is billed at $0.0075 per GiB-hour. A thousand 60-second runs that each use 20 CPU-seconds cost $0.64 (pricing).
A new account gets 100 free hours of a 1 GB machine in its first 30 days, with no card required.
Start
Terminalprintf '%s\n' 'print(sum([125, 250, 375]))' > invoice.pynpx withruntime sandboxes run -- python3 - < invoice.pyThe first run prints a link to approve in your browser. You do not need to copy an API key. Then use the JavaScript or Python SDK as shown above.
Facts on this page were checked on 25 October 2026.