Runtime

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"]); // 23
Pythonfrom 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"])  # 23

The 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).

New accounts get 50 free sandbox hours, no card:

Terminalnpx withruntime sandbox run --trial -- node --version

Sources

Facts on this page were checked on 25 September 2026.