MainSDK
Search⌘K
Interfaces

Security and privacy

Code sandboxes

sandbox

Run untrusted or AI-written code in a sandbox with time, memory and cost limits, no secrets, and only the hosts you allow.

Building for 0.4ships as a beta APIPhase 1 · Core, public betaSpec sandboxFree in your app, Apache-2.0

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

sandbox runs code you didn't write, from a user or a model, outside your app's process, with no secrets, files or network unless you allow them, and stops it at its limits.

Vercel Sandbox and E2B run it in microVMs in production; local runs a limited subprocess for development.

At a glance

sandbox:
  driver: vercel-sandbox
  limits: { timeout: 30s, memory: 512MB, output: 1MB, max-cost: $0.05 }
  network: [pypi.org]

Calls

  • sandbox.run({ code, language, files, network, feature })
  • sandbox.create(opts)
  • sandbox.get(id)
  • sandbox.action({ name, intent, languages })
  • new Sandbox(deps)
  • createSandbox(deps)
  • parseSandboxConfig(raw)
  • secret(name)
  • localSandboxDriver(opts)
  • e2bNetworkPolicy(hosts)
  • vercelNetworkPolicy(hosts)

Guarantees

Whatever the driver.

  • Code never runs in the app's process
  • No network unless allowed, and never wider than network.allow
  • No secrets or app values unless named
  • Killed at its time, memory, output and cost limits
  • Every run audited with the code's hash, never the code
  • The local driver is refused in production

Drivers

3 drivers. Select one to see its line in main.yaml.

main.yamlVercel Sandbox (default)
sandbox:
  driver: vercel-sandbox

Change this one line to swap providers; your calls stay the same.Vercel integration

Codes

CodeWhere it appearsMeaning
MAIN-SANDBOX-0010Checkmainsdk check, before deployeval, new Function or node:vm runs a value that isn't a string literal
MAIN-SANDBOX-0011Checkmainsdk check, before deployThe local sandbox driver in production
MAIN-SANDBOX-0012Checkmainsdk check, before deploysandbox.network lists a host that network.allow doesn't
MAIN-SANDBOX-0001RuntimeError responses and logsA sandbox option is invalid (400); nothing ran
MAIN-SANDBOX-0002RuntimeError responses and logsThe code reached its time limit and was killed
MAIN-SANDBOX-0003RuntimeError responses and logsThe code passed its memory limit and was killed
MAIN-SANDBOX-0004RuntimeError responses and logsOutput or files passed their limit; output is cut
MAIN-SANDBOX-0005RuntimeError responses and logsA host isn't in sandbox.network and network.allow, or the code tried a host it wasn't given (403)
MAIN-SANDBOX-0006RuntimeError responses and logsThe sandbox driver is unavailable (503, retryable)
MAIN-SANDBOX-0007RuntimeError responses and logsThe run would pass, or reached, its cost ceiling (402)
MAIN-SANDBOX-0008RuntimeError responses and logsNo sandbox session with that id in the current tenant, or it was closed (404)
MAIN-SANDBOX-0009RuntimeError responses and logsA secret reference isn't under sandbox.secrets (403)

Features

Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.

FeaturePhaseMilestoneSpec ticket
Local driver with time and memory limits, an empty environment and no networkPhase 10.4sandbox#local-driver
Network allowlist parity with network.allowPhase 10.4sandbox#network-parity
No secrets unless named; files through filesPhase 10.4sandbox#secrets-files
Sessions with runPhase 10.4sandbox#sessions
Vercel Sandbox and E2B driversPhase 10.4sandbox#provider-drivers
A code-runner action for agentsPhase 10.4sandbox#action

Quick start

main.yaml
sandbox:
  driver: local                      # vercel-sandbox | e2b | local (development only)
  local: { python: python3 }
permissions:
  processes: [python3]               # the local driver starts code through the system interface
  disk: { temp: { max: 1GB } }       # and works in a temp folder it removes afterwards
lib/run-snippet.ts
import { sandbox } from "mainsdk";

export async function runSnippet(code: string) {
  const out = await sandbox.run({ code, language: "python", feature: "notebook" });
  return out.stop ? `stopped: ${out.stop}` : out.stdout;
}

language is python, node or bash. feature names the run in the audit event. A run that is stopped still returns a result with stop set; it does not throw.

Configuration

main.yaml
sandbox:
  driver: vercel-sandbox
  limits: { timeout: 30s, memory: 512MB, output: 1MB, files: 50MB, max-cost: $0.05 }
  network: [pypi.org]                # the most any run may get; must also be in network.allow
  secrets: [OPENWEATHER_KEY]         # the only secrets a run may be given
  vercel: { token: "secret:VERCEL_TOKEN", project: prj_123 }
network:
  allow: [pypi.org]
KeyMeaning
driververcel-sandbox, e2b or local. Default local.
limitstimeout (30s), memory (512MB), output (1MB), files (50MB) and max-cost (none). A call can lower a limit, never raise it.
networkHosts any run may be given. Each must also be in network.allow. A call's network defaults to none.
secretsNames of secrets a run may receive through secret(name).
localPrograms for the local driver: { python: python3, node: node }. List them under permissions.processes too.
verceltoken (default secret:VERCEL_TOKEN), project, optional team and runtime.
e2bkey (default secret:E2B_API_KEY) and optional template.
pricePrice per second of a box, such as $0.0001, to override a driver's estimate.

An unknown key under sandbox is refused at load with the key named. The provider's API host is added to the network list for you.

What you get

  • Code never runs in your app's process, and starts with an empty environment, an empty /work holding only the files you pass, and an empty /out.
  • No network by default. Each host you give must be in both sandbox.network and network.allow, or the call fails before anything runs.
  • Output is cut at limits.output and redacted for secret values and known key formats (it comes back as [redacted:secret]).
  • The worst-case cost (price per second times the timeout) is checked against maxCost and limits.max-cost before the run starts.
  • One audit event per run with the SHA-256 of the code, driver, isolation, duration, exit code, stop reason, hosts and env names. Never the code, output or secret values.
  • A provider outage fails only sandbox calls (MAIN-SANDBOX-0006), not the rest of your app.

Examples

Pass files in and read results out

Files you pass appear in the code's working folder (/work); files it writes to the out folder next to it (../out/ on the local driver) come back as FileRefs owned by the caller.

lib/chart.ts
import { sandbox } from "mainsdk";

export async function chart(csv: string) {
  const out = await sandbox.run({
    language: "python",
    feature: "chart",
    files: { "data.csv": csv },
    code: "rows = open('data.csv').read().splitlines()\nopen('../out/count.txt', 'w').write(str(len(rows)))",
  });
  return out.files;   // [{ id, ... }] for count.txt
}

Give a run one secret and one host

lib/weather.ts
import { sandbox, secret } from "mainsdk";

export function weather(code: string) {
  return sandbox.run({
    code,
    language: "python",
    feature: "weather",
    network: ["pypi.org"],
    env: { WEATHER_KEY: secret("OPENWEATHER_KEY") },
  });
}

A name that isn't under sandbox.secrets fails with MAIN-SANDBOX-0009 and nothing runs.

Keep a session open across commands

A session is scoped to the tenant that created it, closes after idle (5 minutes by default), and runs commands as a program and an argument array, never through a shell.

lib/session.ts
import { sandbox } from "mainsdk";

export async function twoSteps() {
  const session = await sandbox.create({ feature: "notebook", idle: "1m" });
  try {
    await session.write("a.txt", "hello");
    const out = await session.run("python3", ["-c", "print(open('a.txt').read())"]);
    return out.stdout;
  } finally {
    await session.close();
  }
}

sandbox.get(id) returns an open session of the current tenant.

Let an agent run code

sandbox.action returns a code-runner action. Its input is { code, language, files? }, its risk is write unless you set it, and an agent calling it passes the agent's budget and approval rules. Its cost counts against the agent's budget.

lib/agent.ts
import { ai, sandbox } from "mainsdk";

const runPython = sandbox.action({
  name: "code.run",
  intent: "Run Python to analyse the user's data",
  languages: ["python"],
});

export function analyse(user: { id: string; roles: string[] }, goal: string) {
  return ai.run({ model: "anthropic/claude-sonnet-4-5", feature: "analysis", agent: "analyst", user, goal, tools: [ai.tool(runPython)] });
}

Reference

Most apps use the sandbox export, which main.yaml builds for you. These are the pieces underneath, for tests and custom wiring.

CallWhat it does
new Sandbox(deps)The interface class. deps takes the driver, the parsed config, the network matcher and a secret resolver, plus optional files, log, tenancy, agents and access.
createSandbox(deps)Builds a Sandbox from the same deps.
parseSandboxConfig(raw)Reads the sandbox section of main.yaml, fills the defaults and refuses an unknown key (MAIN-CONFIG-0003) or an invalid option (MAIN-SANDBOX-0001).
localSandboxDriver({ permissions, programs? })The local driver: a limited subprocess for development and tests. programs sets the python and node paths.
e2bNetworkPolicy(hosts)The E2B egress policy: { allowOut: hosts, denyOut: ["0.0.0.0/0"] }, so only the given hosts are reachable.
vercelNetworkPolicy(hosts)The Vercel Sandbox egress policy: { mode: "deny-all" } for no hosts, otherwise { mode: "custom", allowedDomains: hosts }.

Isolation you can read

Every result has isolation: microvm for the providers, namespace for the local driver on Linux with an empty network, and weak for the local driver otherwise (a proxy refuses other hosts; the code is not in a separate network). Treat weak as a development convenience.

Errors and checks

CodeWhenFix
MAIN-SANDBOX-0001An option is invalid: unknown language, empty code, a limit above sandbox.limits, a path outside /work or /out.Fix the option; nothing ran.
MAIN-SANDBOX-0002 to 0004, 0007The run hit its time, memory, output or cost limit. Returned as stop (timeout, memory, output, cost), not thrown; 0007 throws before the run when the worst case is over maxCost.Raise the limit in main.yaml or lower the work.
MAIN-SANDBOX-0005A host is not in sandbox.network and network.allow, or the code tried a host it wasn't given (logged).Add it to both lists, or drop it from the call.
MAIN-SANDBOX-0006The driver is unavailable (503, retryable).Retry; check the provider and token.
MAIN-SANDBOX-0008No open session with that id in the current tenant.Create a new session.
MAIN-SANDBOX-0009A secret() names a secret that isn't under sandbox.secrets.List it there.

mainsdk check reports MAIN-SANDBOX-0010 for eval, new Function or vm.* on anything but a string literal, MAIN-SANDBOX-0011 for the local driver in production, and MAIN-SANDBOX-0012 for a sandbox.network host missing from network.allow. See check codes.

Swapping drivers

Change sandbox.driver and nothing in your code changes: vercel-sandbox and e2b run code in microVMs through the provider's API (token or key as a secret reference), and local runs a subprocess for development. Every driver gets the same limits, the same network allowlist (enforced through the provider's egress policy) and the same audit event. local in production fails mainsdk check --env prod with MAIN-SANDBOX-0011.

Testing

The local driver needs no account; run the real code in a test and assert on stdout, stop and files. Put the interpreters under permissions.processes in the test's main.yaml. The provider drivers are covered by the conformance suite on recorded sessions.

Security and privacy

  • Code never runs in your app's process.
  • Secrets reach a run only when listed under sandbox.secrets and passed by name; the audit event records names, never values.
  • Every run is audited with the SHA-256 of the code, never the code or its output.
  • Sessions of one tenant can't be reached from another.

Next

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet