Security
This page covers the isolation boundary, permission enforcement, session isolation, and the token model. The last section lists what is not enforced yet.
The isolation boundary
Section titled “The isolation boundary”Each process runs in its own V8 isolate, spawned by Cloudflare’s Worker Loader — the same isolation Cloudflare uses for untrusted code. There is no extra Nimbus sandbox underneath; the isolate is the boundary.
One session is one supervisor Durable Object plus the facet isolates it spawns. A facet receives one capability when it starts: an RPC stub pointing at its own session’s supervisor. Generated facet code cannot import supervisor modules, cannot reach another session’s DO, and cannot mint stubs it wasn’t given. The RPC surface is the only interface it has.
No runtime code generation
Section titled “No runtime code generation”workerd’s content security policy forbids eval and new Function at
request time, and forbids compiling WASM from raw bytes at request time.
Nimbus runs everything — including compilers and interpreters — inside
that constraint: WASM modules enter a facet pre-compiled through the
module map, and nothing in a session can generate and execute new
machine code.
This is why the node and bun REPLs can’t keep declarations between
submits: evaluating a REPL line is runtime code generation, which the CSP
blocks. The REPL banners note it.
Unix permissions, enforced at one seam
Section titled “Unix permissions, enforced at one seam”Every inode stores mode, uid, and gid in the session’s SQLite disk,
so chmod and chown are real mutations that survive hibernation. Every
process carries an immutable credential — uid, gid, groups, umask —
assigned at spawn and inherited from its parent, and every filesystem
operation is bound to that credential before it reaches the VFS. The VFS
exposes no uncredentialed mutation surface, and path resolution checks
traverse permission on every component.
Denials return standard errno: EACCES for access, EPERM for ownership
rules, in every runtime. Root (uid 0) bypasses permission bits as Unix
root does; a normal user does not. The terminal below runs as uid 1000
against a history file owned by root:
Live sandbox — connecting…
Sessions don’t share
Section titled “Sessions don’t share”Loopback never leaves the session. The DNS shim resolves localhost and
nothing else, and 127.0.0.1:<port> is a lookup in the session’s own
port registry — a map inside the supervisor DO — not a network hop. Two
sessions both listening on port 3000 are isolated; neither can reach the
other’s port.
The only thing sessions share is a read-only, content-addressed cache:
npm tarballs keyed by name@version (immutable on the registry since
2018) and runtime blobs, stored in R2. Sessions reach that cache through
narrow supervisor RPC methods — the R2 bindings live on the Worker, not
in any session or facet — so no session can write another session’s view
of a package. There is no cross-session filesystem, no shared scratch
space, and no DO that serves two sessions.
Who can attach
Section titled “Who can attach”Session access is bearer tokens: HS256, verified with a constant-time
comparison against a fixed algorithm — the verifier never reads the
algorithm from the token header. Claims carry a tenant, a subject,
optional scopes (session:create, session:attach, session:destroy,
sandbox:use, …), and an optional sid pin that locks the token to one
session. Attaching to a terminal requires session:attach and a matching
pin; server-minted bootstrap tokens are single-use, consumed atomically
in the DO, so a replayed bootstrap gets a 401. Secrets rotate with a
previous-secret grace window.
The hosted demo’s anonymous sandboxes use the same machinery with tighter limits: a sid-pinned attach token that expires in two minutes, a global cap on concurrent anonymous sessions, a per-address rate limit, and a fixed ten-minute session lifetime.
Fault isolation
Section titled “Fault isolation”The isolate is also the fault boundary. A process that exhausts its 128 MiB heap produces a catchable RPC error: the shell reports it, the session keeps running, and the disk is untouched because facets never own files. The supervisor holds a self-imposed 64 MiB heap ceiling, half the platform cap, because a supervisor OOM resets the session while a facet OOM does not.
Limits
Section titled “Limits”- Permission enforcement is new. The durable
uid/gid/modeschema and the credentialed seam shipped in July 2026. It is probed live, but its coverage is young; treat it as a boundary still being hardened. - Some subsystems run as uid 0. Package installation, runtime staging, and control-plane file operations act as root, the way a system package manager does. Root-owned files in a fresh session come from that, not from a leak.
- Outbound network is open by default. npm and git need it. Per-session egress policy is designed as an embedder option but not shipped, so do not put a sandbox in front of secrets it should not be able to exfiltrate.
- A known upstream availability issue. Certain user-space activity can currently crash the shared workerd process, which resets the sessions running on it. The impact is availability only: sessions reconnect and disks survive, and it is not a path to another tenant’s data. It is reproduced, reported, and being escalated with Cloudflare.
The research page tracks all of these. The hosted demo is for evaluation only: no SLA, and not a place for secrets.