Running the supervisor
The Deno process that actually spawns guest subprocesses, the command to start it, the environment it reads, and the shared secret without which it refuses every run your app sends.
httpSandboxAdapter never executes anything. It validates, authenticates and
forwards. Everything after that, the subprocess and the clamps and the
permission flags, belongs to a second process you deploy yourself.
The command
It runs under Deno only, which is why it ships as source rather than as part of your bundle. Start it as a sidecar beside the app.
export SANDBOX_HOST_ADMISSION_SECRET='at-least-32-random-bytes'
export SANDBOX_BROKER_URL='https://app.internal/api/sandbox/rpc'
deno run --allow-net --allow-env --allow-run --allow-read \
--allow-write=$TMPDIR \
node_modules/@questpie/sandbox/src/sandbox-server.tsIt listens on PORT, default 8787, and exposes exactly two endpoints,
GET /health and POST /run. Point the app's SANDBOX_URL at its origin, with
no path or query, or the adapter rejects the URL before it sends anything.
Environment
| Variable | What it does |
|---|---|
PORT | Listen port, default 8787 |
SANDBOX_HOST_ADMISSION_SECRET | Shared with the adapter, at least 32 bytes. Without a match, /run returns 403 |
SANDBOX_BROKER_URL | The one broker URL it will relay guest app calls to |
DENO_BIN | Deno binary for guest subprocesses, defaults to the supervisor's own executable |
SANDBOX_DISABLE_NETNS_FIREWALL | Set to 1 to skip the Linux network namespace layer |
No secret means no runs, not unauthenticated runs
Every ctx.executor.run() arrives as a host-mode /run. One carrying no
credential, or a secret under 32 bytes, or one that does not match byte for
byte, is refused before a subprocess is spawned. Set the same value on both
sides or the sandbox is inert.
The broker URL is checked, not trusted
Any run that carries bindings is compared against SANDBOX_BROKER_URL before the
spawn. A mismatch in protocol, host, effective port, path or query is a denial,
and userinfo and fragments are forbidden outright. This is what stops a spoofed
caller from redirecting the supervisor, carrying the per-run token, to a host it
chose. Point it at your app's own internal address.
The Linux egress layer
On Linux with unshare, nft and ip present and the capabilities to use them,
each run also gets its own network namespace with a default-drop nftables ruleset
that drops private ranges and accepts only the validated public allowlist. Off
Linux, or when a tool or capability is missing, it logs a one-line notice and the
run proceeds without it.
Treat it as a second layer. The subprocess permission flags are the boundary that is always there, and the ruleset logic is unit-tested while the actual kernel drop is only verified on a real Linux worker.
Remote workloads
httpSandboxAdapter also exposes runWorkload({ envelope }), a separate path
where the source, input and capabilities come from a consumer-supplied authorizer
rather than from your call site. It is authenticated by its own signed admission
rather than by the host secret, and it reads three more variables:
SANDBOX_WORKLOAD_ADMISSION_SECRET, SANDBOX_WORKLOAD_ADMISSION_KEY_ID and
SANDBOX_INSTANCE_ID.
SANDBOX_INSTANCE_ID is a process incarnation, not a deployment or replica name.
Generate a new one for every supervisor process and every restart, and address
that process directly, because the admission is bound to it and its replay state
is process-local.
Next
Sandbox is the adapter side, the capability manifest and the run result.
Adapter options
Every constructor option on the four built-in mail adapters, what each one defaults to, and the Ethereal throwaway inbox for tests.
Brokered app access
How a sandboxed guest reaches your collections and files without importing your app, which route carries the calls, why the guest loses its sockets when you turn this on, and the operations the broker still refuses.