Isolation
An isolation class answers one question: how strong should the walls around this agent be? Your own signed code gets normal walls, code that pulls internet packages or runs LLM-written code gets a syscall-intercepting sandbox, and hostile input gets a full VM with its own kernel. You set the class in the agent spec. If the node cannot provide the wall you asked for, the run is refused, not quietly run somewhere weaker. On Kubernetes the class maps to a runtimeClassName on the pod (ADR 0009).
1. No class declared
The spec leaves isolation unset, which resolves to standard.
- Config
- Scheduler
- Trusted host code
- Untrusted code
Pick a class
Section titled “Pick a class”Pick the least-privileged class the workload can tolerate. Take the first row that matches:
- First-party, signed code:
trusted. - Loads packages from the internet, drives a browser, or runs user or LLM-generated code:
untrusted. - The input itself is adversarial:
hostile. - None of the above:
standard, which is also what an unset class resolves to.
| Class | RuntimeClass | Use when |
|---|---|---|
trusted |
runc |
Signed first-party code only, on shared nodes. No untrusted package loading and no LLM-generated code path. |
standard |
gvisor |
The default. Your own code on shared, gVisor-isolated nodes. |
untrusted |
gvisor plus egress-deny |
Internet packages (PyPI, npm), a browser, or LLM-generated code. Adds an egress allowlist fence (a NetworkPolicy). |
hostile |
Kata (kata-qemu or kata-fc) |
Adversarial workloads. A full-kernel microVM on a dedicated node pool with no co-tenancy. |
wasm |
The configured Wasm RuntimeClass | WebAssembly workloads. |
devcontainer |
gvisor |
A long-lived workspace: a persistent pod and volume that survive across calls. |
The RuntimeClass names are the defaults. The manager reads the actual names from its configuration.
The gate that refuses
Section titled “The gate that refuses”Untrusted and hostile code never runs in a bare pod.
- A node can satisfy
untrustedonly if it advertises the gVisor RuntimeClass. - A node can satisfy
hostileonly if it advertises a Kata RuntimeClass. - A node without the required RuntimeClass fails closed: the run is refused.
untrustedandhostileare never downgraded torunc. standard,devcontainerandwasmalso refuse by default, but the manager can be told to run them on bareruncwithPAL_ALLOW_RUNC_STANDARD=1. That flag never applies tountrustedorhostile.
The requirement is enforced twice. The manager refuses to build the job when the RuntimeClass is not configured, and the pod carries a node selector on the pal.dev/runtimeclass node label, which only nodes labelled as having the handler match.
If your cluster has not provisioned the gVisor and Kata RuntimeClasses, untrusted and hostile workloads are refused. That is correct, but you will see it. The data-plane installer provisions the classes, and a preflight check surfaces a gap before traffic.
In a spec
Section titled “In a spec”Demo 02 (web-scraper) declares untrusted because it pulls third-party packages, and pairs it with an egress allowlist:
spec: isolation: untrusted network: allowlist_domain egress_rules: - pattern: "*.wikipedia.org" http_methods: ["GET"] rate_bps: 1048576 # 1 MiB/sFirecracker and broker mode
Section titled “Firecracker and broker mode”Where the runtime manager uses the Firecracker backend, the workload runs in its own microVM. By default the VM has a network device and the harness enforces the egress allowlist inside the guest. With PAL_EGRESS_MODE=broker on the manager (opt-in, Firecracker only) the VM has no network device, and egress leaves over vsock to a host broker. Kata and Kubernetes backends do not support broker mode. See Egress.
The Firecracker root filesystem is writable. Only the cert and spawn drive is mounted read-only. Inside the VM, the harness runs the workload, exec sessions and shell children as the workload uid, not its own, and withholds its own configuration variables from them.
Kubernetes backend hardening
Section titled “Kubernetes backend hardening”Each workload Job is created with automountServiceAccountToken: false and a default-deny-egress NetworkPolicy. DNS on port 53 is the only exception. The policy is applied before the Job starts and removed with it. Pods run with the RuntimeDefault seccomp profile. The in-VM harness then enforces the declared allowlist on top, so the host policy and the guest allowlist are two independent fences.
# Throwaway kind + Calico cluster: egress deny (incl. 169.254.169.254), seccomp, cap-dropmake k8s-validateThe gVisor and Kata execution legs need a cluster with those RuntimeClasses installed and are skipped without one; GitHub-hosted runners cannot nest-virtualize. Optional cluster policies (Kyverno tenant baseline, cosign image verification, Cilium host egress) live in infra/helm/pal-data-plane/ and are off by default.
Related: Egress, Identity and secrets, Security model.