How to run untrusted code from an LLM safely
Run it in a throwaway microVM with its own kernel, no route to your network, a time limit and a spending cap.
On Runtime that is one call. Every sandbox is a Firecracker microVM with its own Linux kernel. The network can be switched off at create, commands time out on their own, and the sandbox stops when its lease ends. A new sandbox ran its first Python command 351 ms after the request at the median, measured on 24 September 2026 (speed), so a fresh machine per 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", maxCostMicros: 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_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 stops 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 create is refused if its first lease would cost more | maxCostMicros (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 this. 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, so a kernel exploit reaches only a throwaway machine. Firecracker was developed at AWS for services like 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 it 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 at once, to connections already open too. See egress control for what each rule covers.
Keep state between answers
A chat assistant that runs code turn after turn wants its variables to survive. 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 and data frames 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 pass it fails with
spending_limit_reachedand 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, so a retried request never starts a second sandbox.
What it costs
The sandbox above, 2 vCPUs and 4 GiB, costs $0.03125 an hour while it waits and $0.08 an hour with both CPUs busy. Runtime bills the CPU the code uses at $0.025 per vCPU-hour, with a floor of a twentieth of a vCPU, and memory at $0.0075 per GiB-hour. A thousand 60-second runs that use 20 CPU-seconds each cost $0.64 (pricing).
New accounts get 50 free sandbox hours, no card.
Start
Terminalnpx withruntime sandbox run --trial -- python3 -c 'print(6 * 7)'The first run prints a link to approve in your browser. There is no API key to copy. Then use the JavaScript or Python SDK as above.
Facts on this page were checked on 24 September 2026.