How to run JavaScript and Node.js code in a sandbox
Write the script into an isolated Linux microVM that already has Node.js, run node on it with a timeout, and read the output back.
On Runtime, Node.js 24.21.0, npm and Bun 1.4.0 are in every sandbox, so nothing installs before the first run. Each sandbox is a Firecracker microVM with its own kernel, and a new one answered its first command 351 ms after the create request at the median on 24 September 2026 (speed). Runtime bills the CPU the script uses, $0.025 per vCPU-hour, so a sandbox waiting on a slow API costs a fraction of one that computes (pricing).
Run a script
TypeScriptimport { Sandbox } from "withruntime";const script = `const primes = [];for (let n = 2; primes.length < 10; n++) if (primes.every((p) => n % p)) primes.push(n);console.log(JSON.stringify(primes));`;await using sbx = await Sandbox.create({ network: { internet: false }, timeoutSeconds: 300, onLeaseEnd: "stop",});await sbx.files.write("/workspace/job.mjs", script);const run = await sbx.exec(["node", "job.mjs"], { timeoutMs: 20_000 });console.log(run.exitCode, run.timedOut, run.stdout);Pythonfrom withruntime import Sandboxscript = """const primes = [];for (let n = 2; primes.length < 10; n++) if (primes.every((p) => n % p)) primes.push(n);console.log(JSON.stringify(primes));"""with Sandbox.create(network={"internet": False}, timeout_seconds=300, on_lease_end="stop") as sbx: sbx.files.write("/workspace/job.mjs", script) run = sbx.exec(["node", "job.mjs"], timeout_ms=20_000) print(run.exit_code, run.timed_out, run.stdout)An array runs node directly, with no shell, so nothing in the script or its
arguments can inject a command. A script stuck in a loop comes back with
timedOut: true and whatever it printed.
What JavaScript has in the image
| Tool | In the image |
|---|---|
| Node.js | 24.21.0: node, npm, npx |
| Bun | 1.4.0: bun |
| Native modules | gcc, g++, make and Python 3.12 |
| Source | git, curl, wget |
| OS | Ubuntu 24.04.5 LTS (noble), amd64 |
Everything outbound goes through a proxy on the host. The image sets
HTTP_PROXY, HTTPS_PROXY and NODE_USE_ENV_PROXY=1, so npm, fetch and
most HTTP clients find it with no setup
(the network).
Install from npm, then close the network
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ network: { internet: true, allow: ["registry.npmjs.org"] }, timeoutSeconds: 600, onLeaseEnd: "stop",});await sbx.exec("npm init -y && npm install --no-fund --no-audit lodash", { check: true, timeoutMs: 180_000,});await sbx.network.set({ internet: false });await sbx.files.write( "/workspace/job.cjs", "console.log(require('lodash').chunk([1, 2, 3, 4], 2))",);console.log((await sbx.exec(["node", "job.cjs"], { timeoutMs: 20_000 })).stdout);Pythonfrom withruntime import Sandboxwith Sandbox.create( network={"internet": True, "allow": ["registry.npmjs.org"]}, timeout_seconds=600, on_lease_end="stop",) as sbx: sbx.exec("npm init -y && npm install --no-fund --no-audit lodash", check=True, timeout_ms=180_000) sbx.network.set(internet=False) sbx.files.write("/workspace/job.cjs", "console.log(require('lodash').chunk([1, 2, 3, 4], 2))") print(sbx.exec(["node", "job.cjs"], timeout_ms=20_000).stdout)registry.npmjs.org is npm's default registry
(npm). Installing in the
sandbox also keeps whatever a package does at install time off your server.
A stateful JavaScript session
The code interpreter keeps variables between cells, and an array of objects comes back as a table:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ network: { internet: false } });await sbx.interpreter.run("const rows = [{ city: 'Oslo', t: 4 }, { city: 'Rome', t: 19 }]", { language: "javascript",});const cell = await sbx.interpreter.run("rows.map((r) => r.t).reduce((a, b) => a + b)", { language: "javascript",});console.log(cell.results[0]?.data["text/plain"]); // 23Pythonfrom withruntime import Sandboxwith Sandbox.create(network={"internet": False}) as sbx: sbx.interpreter.run("const rows = [{ city: 'Oslo', t: 4 }, { city: 'Rome', t: 19 }]", language="javascript") cell = sbx.interpreter.run("rows.map((r) => r.t).reduce((a, b) => a + b)", language="javascript") print(cell["results"][0]["data"]["text/plain"]) # 23The interpreter runs seven languages. Four are in the default image; the other three install from Ubuntu's archive the first time a sandbox uses them (code interpreter):
| Language | language |
Ready | First use, 23 September 2026 |
|---|---|---|---|
| Python | "python" |
In the image (default) | No install |
| JavaScript | "javascript" |
In the image | No install |
| TypeScript | "typescript" |
In the image | No install |
| Bash | "bash" |
In the image | No install |
| Java | "java" |
Installed on first use | About 35 seconds, once |
| Go | "go" |
Installed on first use | About 35 seconds, once |
| R | "r" |
Installed on first use | About 90 seconds, once |
Serve an app and look at it
For an app rather than a script, start it with spawn, which keeps running
after the call returns, and share the port as a private HTTPS
preview. An exec ends everything it
started when its command ends.
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ timeoutSeconds: 1800 });await sbx.files.upload("./app", "/workspace/app");await sbx.exec("cd app && npm ci", { check: true, timeoutMs: 300_000 });const server = await sbx.spawn("cd app && npm start");console.log(server.id, server.info.state);Bake your dependencies into an image
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({ name: "node-tools", recipe: { npm: ["typescript", "prettier"], apt: ["jq"] },});await using sbx = await runtime.sandboxes.create({ image: "node-tools" });console.log((await sbx.exec(["npx", "prettier", "--version"])).stdout);Pythonfrom withruntime import Runtimeruntime = Runtime()runtime.images.build(name="node-tools", recipe={"npm": ["typescript", "prettier"], "apt": ["jq"]})with runtime.sandboxes.create(image="node-tools") as sbx: print(sbx.exec(["npx", "prettier", "--version"]).stdout)Building an image is free; a stored image is charged on its size (custom images).
Related
- Run TypeScript code in a sandbox
- Run Bun in a sandbox
- Run Deno code in a sandbox
- Preview apps an agent built
- Run Playwright in a sandbox
New accounts get 50 free sandbox hours, no card:
Terminalnpx withruntime sandbox run --trial -- node --versionSources
- npm, the public registry: https://docs.npmjs.com/cli/v11/using-npm/registry (read 25 September 2026)
Facts on this page were checked on 25 September 2026.