# 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](/docs/speed)), so a fresh machine per model answer is practical. ## The short answer ```ts import { Sandbox } from "withruntime"; const code = "print(sum(range(10)))"; // what the model wrote await 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); ``` ```python from withruntime import Sandbox code = "print(sum(range(10)))" # what the model wrote with 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](/docs/security#secrets-sandboxes-never-see) | Root inside the sandbox cannot change any of this. Network rules, CPU, memory and cost are enforced on the host, outside the microVM ([security](/docs/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](https://github.com/firecracker-microvm/firecracker)). The difference is laid out in [microVM vs container](/compare/microvm-vs-container) and [Firecracker vs gVisor](/compare/firecracker-vs-gvisor). ## When the code needs the internet Many answers need a package. Allow only the registries it needs: ```ts check import { 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 runs ``` A rule applies at once, to connections already open too. See [egress control](/glossary/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: ```ts check import { 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.283 ``` Charts come back as PNG and data frames as tables. A full chat assistant built this way is in [code interpreter for chatbots](/use-cases/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_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, 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](/docs/pricing)). New accounts get 50 free sandbox hours, no card. ## Start ```bash no-run npx 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](/docs/javascript) or [Python](/docs/python) SDK as above. Facts on this page were checked on 24 September 2026.