Skip to content
View as Markdown

Isolation and placement ​

Four separate controls, each doing one job. Confusing them is how a multi-tenant deployment ends up with one tenant's sandbox on another tenant's hardware.

1. Placement is scoped to a tenant ​

Every worker carries a workspace. Every create carries a tenant, taken from the credential that made the request — not from the request body.

sandbox(tenant=A) → worker(tenant=A)     ✓
sandbox(tenant=A) → worker(tenant=B)     ✗ never scheduled
sandbox(tenant=A) → worker(tenant="")    ✓ self-hosted fleet, unscoped on purpose

The rule lives in the scheduler, so it applies to every path that creates a sandbox: the native API, the E2B surface, and a resumed delivery after a crash.

2. The worker role can do two things ​

A worker authenticates with a join token (jn_…) and gets a role that reaches exactly two routes: the tunnel handshake, and its own heartbeat. It cannot list the fleet, read a sandbox, or mint anything. Anything else answers 403.

That matters because a join token ships to a machine you may not fully trust — a customer's VM, a contractor's laptop. A compromised worker can run sandboxes; it cannot see the rest of your deployment.

3. Network egress is a policy, not a hope ​

A sandbox can be created with an allow-list. On a Docker worker that is enforced structurally: a per-sandbox internal network with no route out, plus an egress proxy sidecar on a normal network. The sandbox is attached only to the internal one, so it has no path except the proxy.

json
{"allowed_domains": ["pypi.org", "github.com"]}

The policy is applied when the container is created. Setting it on a running sandbox records it for the next creation; it cannot retrofit a network. Details and the trade-offs are in Egress policy.

4. The permission gate (agent and employee-PC mode) ​

In agent mode the sandbox is a real user's machine, so the question is different: not "what can this container reach" but "what may this agent do to my files". That is a six-branch decision engine — hardlocks, mandatory denials, the work tree, permanent grants, session grants, then ask — with every decision written to an audit log, and an optional OS sandbox underneath (bwrap on Linux, seatbelt on macOS) that enforces the same answers at the kernel level.

It is opt-in (-permission-mode prompt|strict), because on a throwaway sandbox it would only add prompts.

What the platform refuses ​

Refusals are features. Each of these is deliberate:

RequestAnswerWhy
A policy on a fleet with no egress-capable worker409 worker capability unavailablethe alternative is a sandbox reported as walled that runs unwalled
A create that would exceed the workspace's concurrent sandbox limit403 from the admission gatethe gate runs at the choke point, so no entry point can skip it
A member trying to delete a worker or mint a key403 owner_requiredspending and access decisions belong to the workspace owner
A sandbox from another tenant, by name404indistinguishable from "does not exist", on purpose

What isolation is not ​

  • Not a VM boundary. Two sandboxes on one worker share a kernel. If you need hardware isolation, give a workspace its own worker (or its own cloud VM).
  • Not a resource guarantee. cpu_cores and memory_mb are scheduler hints; the container limits are Docker's, and a busy worker degrades every sandbox on it.
  • Not protection from the worker. The worker runs Docker: it can see every container it hosts. That is why the join token is scoped and short-lived, and why a customer's own hardware is the strongest tenancy boundary available.

Next ​

Apache 2.0 — your clouds, your machines, your rules.