03/work/sandbox
bas3line/sandbox
Disposable machines for coding agents, with policy enforced by the server instead of the prompt.
I built a self-hosted Rust control plane around one narrow lifecycle: create a bounded environment, execute work, expose only what is intended, collect the result, and destroy it. Humans, CI, and agents use the same authenticated API through the CLI or MCP bridge.
the problem
Coding agents need a real shell, filesystem, ports, and package manager to be useful. Giving them those tools directly on the operator's host makes every prompt injection or mistaken command a host incident.
The boundary needed to be useful to an agent while remaining enforceable by infrastructure the agent cannot rewrite.
constraints
- Treat model output and repository code as untrusted input.
- Bound CPU, memory, PIDs, runtime, output, request bodies, and TTL at the server and runtime layers.
- Never silently weaken requested isolation when compatible capacity is unavailable.
- Support local Docker workers today without pretending containers are hardened hostile multi-tenancy.
architecture
- clientshuman, CI, CLI, MCP, coding agent
- controllerauth, leases, lifecycle, policy
- AEGISrisk score, isolation tier, placement
- statePostgreSQL and optional NATS events
- workerdedicated Docker host or external runtime
- runtimebounded container or VMM-grade driver
stack · Rust · PostgreSQL · Docker · NATS · MCP
decisions
- Policy is server-side
The prompt can request a sandbox; it cannot grant itself more privilege. Auth, limits, placement, network rules, and lifecycle stay in the controller and runtime.
- Risk chooses isolation
AEGIS scores secrets, network access, repository trust, generated code, mounts, privilege, exposure, and lifetime. A high-risk request cannot downgrade itself to a container.
- Commands cross as argv
The API carries structured arguments rather than interpolated shell text. It makes the execution boundary smaller, easier to validate, and less surprising across clients.
- One daemon, separable roles
sandboxd can run controller, worker, or both for development. Production can split roles so the API and the machine holding the runtime socket do not share a blast radius.
results
The repository separates implemented capabilities from v0.1 design boundaries and publishes its architecture, security model, setup guides, CLI, and MCP contract.
failures & lessons
- Docker is an honest first backend
A hardened container on a dedicated single-tenant worker is practical. It is not the final answer for hostile multi-tenancy, so the runtime contract leaves Firecracker, Kata, gVisor, and private VMMs outside the controller.
- Fail closed means no_capacity
If policy requires a microVM and no compatible worker exists, the request fails. Scheduling success is not worth an invisible reduction in isolation.
- v0.1 needs visible boundaries
OIDC/SAML, tenant RBAC, a secret broker, interactive PTY, and raw TCP tunnels are documented as design boundaries rather than implied features.