Sandbox and security
How voxal isolates untrusted app code, refuses shells, guards outbound fetch, and verifies every deploy
voxal runs untrusted app code from many developers on shared infrastructure. The security model rests on one idea: a trusted host holds every dangerous capability, and your app runs in a sandbox that can do pure JavaScript and nothing else, except through two narrow bridges the host provides. For the architecture in full, see How it works.
The host and sandbox split
A trusted host holds the dangerous things: the SSH connections and real network access. Your app code runs inside a sandbox that gets pure JavaScript plus two narrow bridges:
- writing to the terminal
- a guarded
fetch(see fetch)
There is no filesystem, no process spawning, no raw sockets, and no Node built-ins or global
fetch inside the sandbox. Anything dangerous happens on the host, on your app's behalf,
behind a checked boundary.
Per-app isolate
Each app runs in its own isolate: a separate heap and a separate event loop. With true isolation, one app's infinite loop or memory balloon cannot take down another app or the host.
| Property | Value |
|---|---|
| Isolation | One isolate per app (separate heap and event loop) |
| Memory | 64 MiB per app |
| CPU per boot | about 1000 ms of synchronous JS |
| CPU per event | about 100 ms of synchronous JS (connect, key, resize, close, timer) |
| Scale to zero | Dropped after a short idle period, re-booted in a few milliseconds |
The exact runtime numbers live on the Limits and quotas page. The point here is that the boundary is enforced by the runtime itself, not by trusting the app.
No shell, ever
Users connect with:
ssh <appname>@voxal.shThe SSH username is the app name, not a Unix account. No shell is ever spawned. Command execution and PTY shell requests are refused. The SSH channel is wired only to your app's input and output, nothing else.
There is no command execution path. An SSH session can only ever reach your app's
onConnect, onKey, onResize, and onClose handlers. It can never reach a shell, a binary,
or the host filesystem.
Pure JavaScript only
Apps are pure JavaScript. Native npm modules are not allowed, because native code would run outside the isolation boundary. The CLI bundles your app, the SDK, and your pure-JS dependencies into a single file. A dependency that ships native bindings cannot be deployed.
Guarded fetch (SSRF protection)
fetch runs on the host, which means an app-controlled URL is requested from the host's
network position. That is an SSRF surface, so the host applies layered guards:
- Scheme allowlist. Only
httpandhttps. Everything else is refused. - Destination blocking. Private, loopback, link-local, and cloud-metadata addresses are refused, for both hostnames and raw IP addresses.
- Rebinding-safe resolution. The hostname is resolved and validated before the request, and the request is pinned to the checked address so a later re-resolution cannot swap in an internal target.
- Redirect validation. Each redirect hop is re-validated against the same rules.
- Credential stripping. Sensitive headers are dropped on cross-origin redirects.
- Timeout and size cap. A 10 second request timeout and a 5 MiB response cap.
- Rate limit. 30 requests per 10 seconds per app, at most 6 concurrent in-flight.
The response is { status, headers (names lower-cased), body (UTF-8 string) }. A non-2xx
status does not throw, and errors are sanitized so no host details leak to the app. Full
reference: fetch.
Verified deploys
Every deploy is integrity-checked end to end. The CLI checksums the built bundle, the bytes are stored content-addressed, and the checksum is re-verified before your app boots. Because the same checksum is enforced at every step, the bytes that run are exactly the bytes you built, with no chance of tampering in transit.