# How to run untrusted code from an LLM safely Run the code in a disposable microVM. The microVM has its own kernel, no route to your network, a time limit and a spending cap. **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 ```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", 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); ``` ```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_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](/docs/security#secrets-sandboxes-never-see) | 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](/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. A kernel exploit reaches only a disposable machine. AWS developed Firecracker for services such as 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 the code 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 immediately, including to connections that are already open. See [egress control](/glossary/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: ```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 files, and data frames come back 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 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](/docs/pricing)). A new account gets 100 free hours of a 1 GB machine in its first 30 days, with no card required. ## Start ```bash no-run printf '%s\n' 'print(sum([125, 250, 375]))' > invoice.py npx withruntime sandboxes run -- python3 - < invoice.py ``` The first run prints a link to approve in your browser. You do not need to copy an API key. Then use the [JavaScript](/docs/javascript) or [Python](/docs/python) SDK as shown above. Facts on this page were checked on 25 October 2026.