Run untrusted code on your own machine.
One Go binary and Docker. Create a sandbox, run a command, and keep the same API when you need a stronger backend.
import { Sandbox } from "@sbx.torsten.work/sbx"
const box = await Sandbox.create({
template: "python",
ttl: "5m",
})
await box.files.write("/workspace/main.py", "print(2 + 2)\n")
const result = await box.exec(["python", "main.py"])
console.log(result.stdout)
await box.kill()One binary
A Go process, a SQLite file, and the Docker socket.
Closed by default
No network, no capabilities, uid 1000, a read-only rootfs, and a server-side TTL.
Agent inside
Streaming exec, background processes, a PTY, file watches, and preview ports.
Easy now, stronger later
Docker is the default. gVisor is that driver with runsc registered. Kata, Firecracker, and Kubernetes are planned. They keep this API.
| Backend | Isolation | Status |
|---|---|---|
| docker | Namespaces and cgroups. The default, on a machine that already has Docker. | Ships |
| gvisor | The same Docker driver with the runsc runtime. No KVM. A missing runtime is an error. | Ships, optional |
| kata | A lightweight VM. The OCI image stays the same. | Planned |
| firecracker | A microVM per sandbox. | Planned |
| kubernetes | Whatever RuntimeClass the cluster already runs. | Planned |
Build both binaries, then a request
Docker has to be running. Build sbx-agent next to sbx so the agent starts inside the sandbox. The first boot prints an admin key once and writes it to data/initial_api_key.
Templates are base, python, and node. The SDK reads SBX_URL and SBX_API_KEY. From this repo, node --experimental-strip-types examples/quickstart.ts runs on Node, and the same client runs on Bun.
go build -o sbx ./cmd/sbx
go build -o sbx-agent ./cmd/sbx-agent
./sbx serve
curl -s http://127.0.0.1:8080/v1/sandboxes \
-H "Authorization: Bearer $SBX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"template":"base"}'
curl -s http://127.0.0.1:8080/v1/sandboxes/$ID/exec \
-H "Authorization: Bearer $SBX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"cmd":["echo","hello"]}'Install a release
A version tag publishes the binary, ghcr.io/torstendittmann/sbx, and @sbx.torsten.work/sbx. The npm name sbx is a different project.
Linux installs sbx and sbx-agent. macOS installs a darwin server and a linux agent, because the agent runs in the container. The image ships both and sets SBX_AGENT_BIN.
curl -fsSL https://raw.githubusercontent.com/TorstenDittmann/sbx/main/install.sh | sh
sbx version
docker run --rm -p 8080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v sbx-data:/data \
ghcr.io/torstendittmann/sbx:latest
npm install @sbx.torsten.work/sbx
bun add @sbx.torsten.work/sbxThe calls you make
Send Authorization: Bearer sbx_…. Keys are stored hashed. A sandbox key only sees sandboxes it created. Processes, terminals, watches, and previews need sbx-agent. Without it those routes return unsupported, and exec and files use a shell in the image.
| POST | /v1/sandboxes | create |
|---|---|---|
| POST | /v1/sandboxes/{id}/exec | wait, or SSE with ?stream=1 |
| POST | /v1/sandboxes/{id}/processes | background command |
| POST | /v1/sandboxes/{id}/terminals | PTY, then a WebSocket |
| GET | /v1/sandboxes/{id}/files/watch | file events |
| POST | /v1/sandboxes/{id}/previews | a port on the sandbox loopback |
| PUT | /v1/sandboxes/{id}/files/content | upload |
| GET | /healthz | Docker reachable, or 503 |
| GET | /openapi.yaml | the contract |
Limits
network: noneis the default.fullandallowreach the public internet, with DNS, allow and deny lists, and per-sandbox bandwidth. Metadata and private ranges stay blocked unless the operator opts out. Previews can be public or token-protected, including{port}-sbx-{id}.example.com.disk_mbis the size of the writable/workspacetmpfs. The root filesystem is read-only, memory has no swap, and a PID limit applies.- Archives call
tarin the image. Pause, resume, templates, quotas, and/metricsare real. A multi-node scheduler is not.