sandbox-bridge
$
npx mdskill add cloudflare/sandbox-sdk/sandbox-bridgeExecute live Sandbox containers via HTTP without local deployment.
- Validate SDK changes against a running container instantly.
- Integrates with the bridge worker and injected API credentials.
- Decides availability by checking for required environment variables.
- Delivers results through a small HTTP API for scripts or tests.
SKILL.md
.github/skills/sandbox-bridgeView on GitHub ↗
---
name: sandbox-bridge
description: Use when you need to exercise a real, running Sandbox deployment via HTTP — for example to validate SDK changes against a live container, reproduce a user-reported issue, or experiment with the API (including FUSE bucket mounts) without spinning up `wrangler dev`. Documents the Sandbox bridge worker reachable via `SANDBOX_WORKER_URL` + `SANDBOX_API_KEY` when the host injects them.
---
# Sandbox Bridge
A hosted Cloudflare Sandbox deployment _may_ be available to agents working in this repo, depending on whether the host injects credentials for it. It exposes the full `@cloudflare/sandbox` SDK over a small HTTP API ("the bridge") so you can drive a real sandbox container from `curl`, scripts, or tests without deploying your own worker.
The source for the bridge lives in the repo:
- `bridge/worker/` — the deployed worker entrypoint (thin wrapper).
- `packages/sandbox/src/bridge/` — the actual bridge implementation: routes, auth, pool management.
If the API behaves unexpectedly, read those before guessing.
## Credentials
When the host provides them, two environment variables are set in your shell:
| Variable | Purpose |
| -------------------- | ---------------------------------------- |
| `SANDBOX_WORKER_URL` | Base URL of the bridge worker (https). |
| `SANDBOX_API_KEY` | Bearer token for `Authorization` header. |
If either is unset, the bridge isn't available for this session — fall back to `wrangler dev` against an example, or ask the user to enable it.
All requests require `Authorization: Bearer $SANDBOX_API_KEY`. Missing/invalid tokens return `401 unauthorized`. **Always pass the token via the header — never via a query string — to keep it out of access logs and shell history.**
## OpenAPI Spec
The full, authoritative spec is served by the bridge itself:
```bash
curl -sf -H "Authorization: Bearer $SANDBOX_API_KEY" \
"$SANDBOX_WORKER_URL/v1/openapi.json" | jq '.paths | keys'
```
## Typical Flow
The bridge is stateless from the client's point of view: each sandbox is identified by an opaque ID returned from `POST /v1/sandbox`. Use that ID for every subsequent `/v1/sandbox/{id}/*` call, then `DELETE` it when done.
### 1. Create a sandbox
```bash
SID=$(curl -s -X POST "$SANDBOX_WORKER_URL/v1/sandbox" \
-H "Authorization: Bearer $SANDBOX_API_KEY" | jq -r .id)
echo "$SID" # e.g. nmghbg45psadoawxuazxrfr23e
```
### 2. Exec a command (SSE stream)
`POST /v1/sandbox/{id}/exec` streams output as Server-Sent Events. The body takes an `argv` array — already shell-split — so wrap shell snippets in `["sh","-lc", "..."]`.
```bash
curl -sN -X POST "$SANDBOX_WORKER_URL/v1/sandbox/$SID/exec" \
-H "Authorization: Bearer $SANDBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"argv":["sh","-lc","echo hello; uname -a"]}'
```
Events emitted:
| Event | `data` payload | Notes |
| -------- | ------------------------------------- | ------------------------------- |
| `stdout` | base64-encoded chunk of stdout | May fire many times. |
| `stderr` | base64-encoded chunk of stderr | May fire many times. |
| `exit` | `{"exit_code": N}` (JSON) | Terminal — stream closes after. |
| `error` | `{"error":"...","code":"..."}` (JSON) | Terminal — replaces `exit`. |
Decode stdout/stderr with `base64 -d`. Optional request fields: `timeout_ms` (per-call timeout) and `cwd` (must resolve under `/workspace`).
A small helper to print decoded stdout:
```bash
curl -sN -X POST "$SANDBOX_WORKER_URL/v1/sandbox/$SID/exec" \
-H "Authorization: Bearer $SANDBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"argv":["sh","-lc","ls /workspace"]}' \
| awk '/^event: /{ev=$2} /^data: /{sub(/^data: /,""); if(ev=="stdout") print | "base64 -d"; else if(ev=="exit"||ev=="error") print "[" ev "] " $0}'
```
### 3. Read / write files
Files live under `/workspace` inside the sandbox. The path in the URL is given **without** the leading slash and must resolve within `/workspace`.
```bash
# Write
echo 'print("hi")' | curl -s -X PUT \
"$SANDBOX_WORKER_URL/v1/sandbox/$SID/file/workspace/main.py" \
-H "Authorization: Bearer $SANDBOX_API_KEY" \
-H "Content-Type: application/octet-stream" \
--data-binary @-
# Read
curl -s -X GET \
"$SANDBOX_WORKER_URL/v1/sandbox/$SID/file/workspace/main.py" \
-H "Authorization: Bearer $SANDBOX_API_KEY"
```
### 4. Destroy
Always clean up. Destroying an unknown ID is a no-op (`204`).
```bash
curl -s -X DELETE "$SANDBOX_WORKER_URL/v1/sandbox/$SID" \
-H "Authorization: Bearer $SANDBOX_API_KEY" -w "%{http_code}\n"
```
## Sessions
Every sandbox has a **default session** that backs exec/file/PTY calls when no `Session-Id` header is set. Sessions isolate two things across commands:
- **Working directory** — `cd` in one exec persists for subsequent execs in the same session.
- **Environment variables** — `export FOO=bar` likewise persists, and `env` passed at session creation seeds the session.
Use named sessions when you need parallel execution contexts in the same sandbox (e.g. a long-running build in one and quick probes in another) without them clobbering each other's `cwd`/env.
### Create a session
```bash
SESS=$(curl -s -X POST "$SANDBOX_WORKER_URL/v1/sandbox/$SID/session" \
-H "Authorization: Bearer $SANDBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"cwd":"/workspace","env":{"NODE_ENV":"test"}}' | jq -r .id)
```
The body is optional. You can also pass `id` to choose your own (must match `^[a-zA-Z0-9._-]{1,128}$`); otherwise one is generated for you.
### Use a session
Pass the ID via the `Session-Id` header on `exec`, file read/write, or `pty`:
```bash
curl -sN -X POST "$SANDBOX_WORKER_URL/v1/sandbox/$SID/exec" \
-H "Authorization: Bearer $SANDBOX_API_KEY" \
-H "Session-Id: $SESS" \
-H "Content-Type: application/json" \
-d '{"argv":["sh","-lc","cd src && pwd && echo $NODE_ENV"]}'
# A second exec in the same session inherits cwd=/workspace/src and NODE_ENV=test:
curl -sN -X POST "$SANDBOX_WORKER_URL/v1/sandbox/$SID/exec" \
-H "Authorization: Bearer $SANDBOX_API_KEY" \
-H "Session-Id: $SESS" \
-H "Content-Type: application/json" \
-d '{"argv":["sh","-lc","pwd"]}'
```
Invalid session IDs return `400 invalid_request`. Unknown but well-formed IDs are created on first use by some routes — prefer explicit `POST /session` so you control `cwd`/`env`.
### Delete a session
```bash
curl -s -X DELETE "$SANDBOX_WORKER_URL/v1/sandbox/$SID/session/$SESS" \
-H "Authorization: Bearer $SANDBOX_API_KEY"
```
The default session cannot be deleted (`502 session_error`). Sessions also disappear when the parent sandbox is destroyed.
## Other Endpoints
These exist on the bridge — consult `/v1/openapi.json` for full schemas before using them:
| Path | Purpose |
| ------------------------------------------- | ----------------------------------------------- |
| `/health` | Liveness probe. |
| `/v1/pool/{prime,stats,shutdown-prewarmed}` | Pre-warm pool management. |
| `/v1/sandbox/{id}/pty` | Interactive PTY stream. |
| `/v1/sandbox/{id}/running` | List running processes. |
| `/v1/sandbox/{id}/{mount,unmount}` | Mount / unmount S3-compatible buckets via FUSE. |
| `/v1/sandbox/{id}/{hydrate,persist}` | Workspace persistence ops. |
## Error Codes
Errors return JSON `{ "error": "...", "code": "..." }` with one of:
`unauthorized`, `invalid_request`, `exec_error`, `exec_transport_error`,
`workspace_read_not_found`, `workspace_archive_read_error`,
`workspace_archive_write_error`, `capacity_exceeded`, `pool_error`,
`mount_error`, `unmount_error`, `session_error`.
Once an `exec` SSE stream is open, transport errors arrive as `event: error` instead of an HTTP error.
## When to Use This vs. `wrangler dev`
- **Bridge** — fastest path to "does this command behave correctly inside a real sandbox container?". No local Docker, no build step. Also the only option for features that depend on host-level capabilities the local dev loop doesn't replicate, notably **FUSE-based bucket mounts** (`/v1/sandbox/{id}/mount`) — `wrangler dev` cannot mount s3fs-FUSE filesystems.
- **`wrangler dev`** (see the `examples` skill) — required when iterating on the container image, the worker code, or anything that isn't already deployed to the bridge.
The bridge runs whatever version of `@cloudflare/sandbox` is currently deployed to it; it is **not** automatically updated from your working tree. If you need to test unreleased SDK changes that don't require FUSE, use `wrangler dev` against a local example instead.
More from cloudflare/sandbox-sdk
- architectureUse when navigating the codebase for the first time, adding a new client method, adding a new container handler/service, or understanding how a request flows from Worker through the Sandbox DO into the container. Covers the three-layer architecture, client pattern, container runtime structure, and monorepo layout. (project)
- changesetsUse when creating a changeset, preparing a release, or bumping versions. Covers which packages to reference, how to write user-facing changeset descriptions, the release automation flow, and the npm/Docker version sync requirement. (project)
- coding-standardsUse when writing or reviewing TypeScript in this repo. Covers the no-`any` rule and where to put new types, the uppercase-acronym style guide, and the rules for code comments (no historical context). (project)
- examplesUse when working in the examples/ directory, running an example with wrangler dev, adding a new example, or answering questions about EXPOSE directives and the local Docker dev loop. (project)
- git-commitUse when creating git commits to ensure commit messages follow project standards. Applies the 7 rules for great commit messages with focus on conciseness and imperative mood.
- loggingUse when adding logs, debugging, or working with the Logger across the SDK and container runtime. Covers the constructor-injection pattern, child loggers, env-var configuration, and test mocking. (project)
- session-executionUse when working on or reviewing session execution, command handling, shell state, FIFO-based streaming, or stdout/stderr separation. Relevant for session.ts, command handlers, exec/execStream, or anything involving shell process management. (project)
- testingUse when writing or running tests for this project. Covers unit vs E2E test decisions, test file locations, mock patterns, and project-specific testing conventions. (project)