Security
What isolates what, and what doesn't.
The runner runs code that you or your agents wrote. It is not a sandbox for code from unknown or untrusted people: workerd describes itself as not a hardened sandbox, and every service runs as the same user in one container. To host untrusted code, run one runner per tenant, each isolated from the others: a VM, or a container with a sandboxed kernel such as gVisor. That is how Zeroko Cloud runs workspaces: a runner each, in its own gVisor sandbox.
What isolates what
- Services from each other. Each service runs in its own workerd
process, with its own heap limit: one service's crash, leak or endless
loop stops only that service. Services reach data only through their
bindings: each process has a random token that the runner maps to its
service, and binding names come from the process's config, not from the
service's code. A service reaches another only through a
service:binding it declares. - Services from the runner. A service's code cannot read the token or
reach the control socket: both live in a separate Worker of the process.
node:fssees a virtual file system (the service's modules and a temporary directory), not the container's disk. - The network from services. Outbound requests and sockets go through the runner's egress proxy, which connects to public addresses only, checked after DNS: never loopback or private networks, so services cannot reach the runner's listeners or the host's network. Each service can be limited to a list of hosts, or have no outbound access at all.
- The disk from services. Each service has a quota for its databases and files together.
- The files from service SQL. An authorizer refuses
ATTACHandDETACH, transactions outsidebatch(), extension loading and the pragmas that change storage. - The API from services. Services are served on subdomains, and the API
takes only bearer tokens, never cookies, so a service's page cannot act on
the API for its visitor. Files served by the API carry
Content-Security-Policy: sandbox.
The container
The image holds only the two binaries, the C library and CA certificates.
The runner starts as root only to give /data to its user (1000 by
default), then drops to it before anything else, with no supplementary
groups; workerd runs as the same user.
Access
- The root token, from
ZEROKO_TOKEN, is for the app that manages the runner. It is compared by its SHA-256, in constant time. - API tokens (
zk_…) are for agents. They have full access to every service. The runner stores only their SHA-256 and shows each one once. - The runner has no users or sign-in: the panel in front of it does. The open source panel has one admin, with a password; the root token never reaches the browser.
Secrets
Service secrets are encrypted with AES-256-GCM, bound to their service, with
/data/master.key, created on first start. They are decrypted only to start
a service's process and never returned by the API. Anyone with the volume or
a backup has the key: protect both.
Not covered
- Rate limits are per client address (
rate_limit, off by default): a spread-out flood needs the proxy or firewall in front. - TLS on sockets from
connect(): workerd doesn't proxy it yet. Plain TCP works, and HTTPS goes throughfetch.