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.
Two shapes, one harness#
| Agent sandbox | Shell sandbox | |
|---|---|---|
| Image | Yours | Platform-provided |
| Contains | Your agent and its tooling | aws, kubectl, gh, jq, yq, ripgrep, webfetch, git, curl, make, gcc, mise |
| Created by | sandbox create |
The platform, one per project |
| Used for | Agents that run in your cluster | Tool calls on behalf of an agent running elsewhere |
| Reached by | sandbox exec, exposed ports |
shell exec, MCP agent tools |
The isolation harness below is identical in both. What differs is who owns the image.
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
allowordenyper pattern, defaulting to deny. - A transport of
upstream— routed through the platform so credentials can be injected and the call audited — ordirect, 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.
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. sandbox.k8s.activeDeadlineSeconds bounds how long a sandbox pod runs before it is stopped, which limits the exposure window if something does go wrong.
Related#
- Sandboxes — creating and running them
- How Lens Agents works — where the sandbox sits in the architecture
- Policies — what the proxy enforces
- Security model — threat model and trust boundaries
- Security whitepaper — the full document for a security review