Skip to content

Sandbox isolation#

A sandbox is the boundary an agent runs inside. It is built from Linux kernel primitives rather than agent cooperation, which is the important property: an agent cannot opt out of it, misconfigure its way past it, or be talked out of it by a prompt.


Your image, the platform's harness#

The isolation harness below is the same whatever the image contains.


The layers#

Unprivileged execution#

The agent process runs as an unprivileged sandbox user, with setuid/setgid applied and all supplemental groups dropped. Once dropped, there is no path back to root.

The supervisor and egress proxy run as root because they enforce the boundary — they install the firewall rules and terminate the connections. The untrusted component never holds those privileges. That split is deliberate.

Kernel-level network lockdown#

Per-sandbox nftables rules capture every network call the agent can make:

  • TCP is redirected to the local proxy, which classifies each connection as TLS or plain HTTP, applies the policy, and forwards or drops it. This works whether or not the tool honours HTTPS_PROXY, and whatever port it uses. Unknown protocols are dropped with a deny event.
  • DNS is redirected to a local stub resolver applying the same domain policy. Allowed names resolve through the host resolver; denied names return NXDOMAIN. A blanket DNS allow would be a covert channel, so there is not one.
  • Loopback and established flows are permitted, because the proxy needs them.
  • Everything else is rejected in the kernel.

IPv4 and IPv6 are both hardened, and there is no switch to turn any of it off. The sandbox user has exactly one reachable destination: the local proxy.

The node kernel must support nf_tables

A node whose kernel lacks nf_tables runs the platform but fails every sandbox. Managed Kubernetes and local clusters that share your host kernel have it; VM-driver minikube does not. See Install on Kubernetes.

Proxy-mediated egress#

Every outbound connection terminates at the sandbox's own proxy, which enforces the effective policy:

  • Domain matching, with wildcards, first match wins.
  • A verdict of allow or deny per pattern, defaulting to deny.
  • A transport of upstream — routed through the platform so credentials can be injected and the call audited — or direct, allowed without interception.
  • HTTP-level rules, matching method and URL path, so read-only access to an API is expressible rather than approximated.
  • TLS interception on governed domains, via an ephemeral per-sandbox certificate authority, which is what makes credential injection possible on HTTPS.

upstream routes go through the platform's forward proxy, which checks each CONNECT against the effective policy again. A host that the policy does not route upstream is refused with 403 and written to the audit trail, so a proxy credential taken out of the sandbox reaches no more than the sandbox can. When the policy cannot be resolved, the connection is refused too.

The forward proxy also refuses some targets whatever the policy's domain rules say. A direct route does not reach the forward proxy, and a cluster tunnel does not pass through this check. A connection to loopback, to a link-local address (the cloud metadata endpoints included), or to the platform server's own address is refused with 403. A hostname is checked against every address it resolves to, so a name that points at one of those addresses is refused too. Other private ranges stay reachable, because a sandbox legitimately reaches services inside the cluster.

Credential injection#

Credentials never exist inside the sandbox. The agent's environment holds a placeholder. The proxy substitutes the real value into the request as it leaves, on the domains policy names, and records the injection.

An agent that dumps its own environment, reads its own filesystem, or is tricked into exfiltrating its configuration leaks placeholders. See Credentials.

Filesystem#

The agent sees the unprivileged user's view: no elevated access to host mounts, no writes outside its workspace. The container filesystem is discarded when the sandbox stops. Anything that must survive goes on a persistent volume.


Host isolation#

By default a sandbox pod shares the node kernel. Set sandbox.k8s.runtimeClassName to a Kata Containers or gVisor runtime class and each sandbox gets its own kernel boundary instead.

Default With a microVM runtime class
Unprivileged execution Yes Yes
nftables lockdown Yes Yes
Proxy-mediated egress Yes Yes
Credential injection Yes Yes
Kernel boundary Shared with the node Per sandbox
Syscall exposure Any syscall an unprivileged user can make Bounded by the hypervisor

This is the single highest-value hardening step for a production install, and it is one Helm value. Configure it in Install on Kubernetes.


What the sandbox does not do#

The harness does not apply seccomp syscall filtering. Isolation rests on unprivileged execution and kernel-level network lockdown, so an agent can still make any syscall available to an unprivileged user. Where syscall-level restriction is required, run sandboxes under a microVM runtime class and let the hypervisor provide that boundary.

Being explicit about this is the point. See Security model for the full threat model.


Lifecycle#

Resource requests are per sandbox, bounded by install-wide ceilings — a request over the ceiling is rejected, not silently clipped. A sandbox has no wall-clock deadline: it runs until it is stopped or deleted, by a person or by its idle timers. For a job that should not outlive its command, sandbox run --rm deletes the sandbox when the command ends. To cut every running sandbox off at once, use the emergency halt.

A sandbox's first process handles no SIGTERM, so the pod gets a 5-second termination grace rather than the Kubernetes default of 30. A pod that Kubernetes or the agent-sandbox controller stops is killed within those 5 seconds. A pod the platform deletes itself is removed at once.