On Runtime, every command runs in bash inside its own microVM. Nothing needs to
be installed. A string sent to exec runs under bash -c as the user
runtime, with passwordless sudo. The sandbox is a Firecracker microVM that
runs Ubuntu 24.04.5. Root in the sandbox still cannot change the sandbox's
network, CPU, memory or cost (security).
Four ways to run shell code
| You have | Call | What runs it |
|---|---|---|
| A one-line command | exec("ls *.txt && date") |
bash -c, so globs, && and pipes work |
| A program and its arguments | exec(["grep", "-rn", pattern, "src"]) |
The program directly, no shell |
| A script file | files.write then exec(["bash", "job.sh"]) |
bash, reading the file |
| Commands that share state | interpreter.run(code, { language: "bash" }) |
A bash session that persists |
Use the array form whenever part of the command comes from a user or a model.
With no shell, a value such as ; rm -rf ~ is only an argument
(run commands).
The image has bash, git, curl, wget, jq, rg, fd,
sqlite3, zip, unzip, xz, gcc and make
(the sandbox environment).
Ubuntu 24.04's bash package is 5.2.21 (Ubuntu's archive, 26 October 2026).
Run a script an agent wrote
Write the script as a file. Run it with strict mode on, so the first failing line stops the script, and the exit code reports the failure.
TypeScriptimport { Sandbox } from "withruntime";const script = `set -euo pipefailmkdir -p outfor n in 1 2 3; do echo "item $n" >> out/list.txt; donewc -l < out/list.txt`;await using sbx = await Sandbox.create({ network: { internet: false }, timeoutSeconds: 300, onLeaseEnd: "stop",});await sbx.files.write("/workspace/job.sh", script);const run = await sbx.exec(["bash", "job.sh"], { timeoutMs: 30_000 });console.log(run.exitCode, run.timedOut, run.stdout, run.stderr);Pythonfrom withruntime import Sandboxscript = """set -euo pipefailmkdir -p outfor n in 1 2 3; do echo "item $n" >> out/list.txt; donewc -l < out/list.txt"""with Sandbox.create(network={"internet": False}, timeout_seconds=300, on_lease_end="stop") as sbx: sbx.files.write("/workspace/job.sh", script) run = sbx.exec(["bash", "job.sh"], timeout_ms=30_000) print(run.exit_code, run.timed_out, run.stdout, run.stderr)Each result returns the following, and these settings protect you:
- Exit code, stdout and stderr separately, for the agent to read.
- A time limit:
timeoutMs, which is 60 seconds by default and 24 hours at most. A script that hangs returnstimedOut: truewith the output it produced so far. - An output cap: at most 64 KiB of stdout and 64 KiB of stderr per result.
stdoutTruncatedis set when output was dropped. For more output, redirect it to a file and read the file withfiles.readText. - No route out with
internet: false. Private addresses are refused even when the internet is on.
Pass secrets and input safely
Put a secret in env, never in the command text. Runtime never echoes it back,
and journals record a hash, not the value. stdin feeds the command, then closes.
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create();const count = await sbx.exec(["bash", "-c", 'grep -c "$1"', "count", "error"], { stdin: "ok\nerror: disk\nerror: net\n",});console.log(count.stdout); // 2await sbx.exec(["bash", "-c", 'test -n "$DEPLOY_TOKEN"'], { env: { DEPLOY_TOKEN: process.env.DEPLOY_TOKEN ?? "" },});The first call passes the pattern as $1, so the pattern is never parsed as shell code.
For an API key that the code should use but never read, store the key as a
secret the sandbox never sees.
Keep a shell session between commands
Each exec starts a new bash -c, so an export does not carry over to the
next call. The code interpreter's Bash keeps its state from one run to the next:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ network: { internet: false } });await sbx.interpreter.run("export STAGE=build", { language: "bash" });const cell = await sbx.interpreter.run('echo "stage: $STAGE"', { language: "bash" });console.log(cell.stdout); // stage: buildPythonfrom withruntime import Sandboxwith Sandbox.create(network={"internet": False}) as sbx: sbx.interpreter.run("export STAGE=build", language="bash") cell = sbx.interpreter.run('echo "stage: $STAGE"', language="bash") print(cell["stdout"])For a real terminal with colors and prompts, run sbx.terminal() or
npx withruntime sandboxes shell <id>. See the
code interpreter for how sessions work.
Lint scripts before running them
ShellCheck is an Ubuntu package (shellcheck 0.9.0 on noble, 26 October
2026). Lint the script first, and give the warnings to the model that wrote it:
Pythonfrom withruntime import Sandboxwith Sandbox.create(timeout_seconds=600, on_lease_end="stop") as sbx: sbx.exec("sudo apt-get update -q && sudo apt-get install -y -q shellcheck", check=True, timeout_ms=300_000) sbx.network.set(internet=False) sbx.files.write("/workspace/job.sh", 'for f in $(ls *.txt); do cat $f; done\n') lint = sbx.exec(["shellcheck", "-f", "gcc", "job.sh"]) print(lint.exit_code, lint.stdout)To have ShellCheck in every sandbox, build an image with
recipe: { apt: ["shellcheck"] } (custom images).
Run the same command from your terminal
Terminalnpx withruntime sandboxes run -- bash -c 'uname -a; df -h /workspace'The first run prints a link to approve in your browser. There is no key to copy. A new account gets 100 free hours of a 1 GB machine in its first 30 days, with no card required. A 2 vCPU, 4 GiB sandbox costs $0.03125 an hour while it waits, and $0.08 an hour when fully busy (pricing).
See also running untrusted LLM code, a coding agent sandbox and turning off a sandbox's internet.
Sources
Checked 26 October 2026.
- Ubuntu 24.04 (noble) packages: bash, shellcheck
Facts on this page were checked on 26 October 2026.