Containment backends
A containment backend is the substrate that provides Lever’s jail. The jail is a contract, not a single product, and one of its guarantees decides whether a backend can even exist in code.
The jail contract
- A hypervisor boundary between the agent workload and the host kernel. Mandatory. Agents run
arbitrary, potentially adversarial code with real credentials (see the threat model in
security model §1); a network/mount/user namespace is not a substitute for a
separate kernel, one kernel-level exploit from any agent reaches everything sharing that kernel.
No backend without a VM boundary is added to
lever backends. - No host filesystem beyond the one chosen project tree.
- A network namespace Lever controls, so the egress allowlist can be enforced outside the agent containers.
- Egress enforced in that namespace, not by the agent behaving.
- A host-reachable capability-broker endpoint for capability, LLM, and tool traffic.
See security model §2.4 for the full contract in context.
Run lever backends to print the live matrix; this page mirrors it. A candidate exists in
code iff a registry constructor exists for it. Select a backend with the
backend: key in lever.yaml; an unrecognised name is rejected at config
load rather than silently substituted, so a containment posture is never quietly swapped for
another.
Implemented
| Backend | Kernel | FS bounded by | Egress enforced at | Version-fragile |
|---|---|---|---|---|
orbstack |
shared | isolated machine: no host files + project tree mounted at /lever |
jail netns iptables/ip6tables | yes |
lima |
separate | VM: no host files + project tree mounted at /lever |
jail netns iptables/ip6tables | yes |
(Columns mirror lever backends’ own output.)
orbstack — reference
macOS on Apple Silicon with OrbStack. The runtime, the Scion server/broker, rootless podman, and every agent run inside one OrbStack isolated machine that shares no host files and has its own network namespace. This is the reference substrate the other backends are measured against. Its trade: a single kernel shared across the manager and all workers (a kernel-level container escape reaches the whole jail — see security model §8).
lima — the non-OrbStack path
The Lima VM backend: macOS (vz) and Linux (QEMU/KVM), for anyone who does
not run OrbStack. It preserves the VM boundary, its own kernel, not shared with the host or with
other jails, so its guarantees match orbstack’s. The containment surface is a lever-owned
template (stock Lima templates are not used), built from three mechanisms:
- Exactly one writable mount: the project tree, at
/lever. Nothing else, in particular not Lima’s stock~read-only home mount. - All automatic guest→host port-forwarding suppressed. Lima’s default forwards every guest
listener to the host’s
127.0.0.1: on a stock template, a guest-side0.0.0.0listener is reachable at the host’s loopback. Left on, a jailed agent could squat a host-loopback port and impersonate a local service (a dev server, a credential helper). The lever template’sportForwardsblock carries an ignore-all rule for both0.0.0.0and127.0.0.1guest binds, closing it. - Lima’s bundled containerd disabled (
containerd: {system: false, user: false}). Lever provisions rootless podman/Docker itself, exactly as it does fororbstack.
Requires Lima ≥ 2.0.0, checked at bring-up: the ignore rules depend on Lima 2.0’s
portForwards semantics, and an older limactl forwards guest ports despite the rendered ignore
rules. The template sets guestIPMustBeZero: true explicitly, so the containment property does
not depend on Lima’s auto-inference.
host.lima.internal (resolving to 192.168.5.2) is the host alias, the direct analog of OrbStack’s host.orb.internal: it’s how an agent
reaches the broker and any allowlisted host tool port.