LanternDOCS

Isolation Classes

Every headless agent declares an isolation class in its agent.yaml. The class is the security boundary the workload runs behind. On Kubernetes it maps to a runtimeClassName — isolation is a tier on the pod, not a separate backend (ADR 0009).

In plain termsAn isolation class answers one question: how strong should the walls around this agent be? Code you wrote yourself gets normal walls. Code that pulls packages from the internet or was written by an LLM gets a sandbox that fakes the operating system. Truly hostile input gets a full virtual machine — its own kernel, its own everything. You declare the level once in the agent's config; if the cluster can't provide walls that strong, Lantern refuses to run the workload rather than quietly using weaker ones.
The isolation ladder you declare how much you trust the code — the runtime either honors it or refuses. There is no third option. WHAT YOU DECLARE — THE TRUST CLASS trusted First-party code you wrote any backend, plain runc allowed standard Normal workloads (default) gVisor; runc only if explicitly allowed untrusted User-supplied / generated code REQUIRES a separate kernel (gVisor) hostile Assume it will attack the host REQUIRES a hypervisor (Kata / Firecracker) FAIL-CLOSED GATE — twice choose_backend() refuses, then build_job() re-checks — never a bare pod WHAT ACTUALLY RUNS IT — THE BACKEND LADDER, WEAKEST TO STRONGEST runc shared kernel, namespaced trusted · (standard only with LANTERN_ALLOW_RUNC_STANDARD — default false) gVisor userspace kernel between code and host syscalls standard · untrusted RuntimeClass: gvisor Kata lightweight VM per pod — its own kernel + hypervisor hostile and below RuntimeClass: kata Firecracker microVM with the Lantern harness as PID 1 inside all classes · session VMs · in-guest tool runner Wasmtime capability-scoped sandbox for wasm workloads refuses untrusted / hostile outright When the hardened class is missing, your workload does not run — and that is the feature. sandbox class missing from the cluster → capability disabled, gate still refuses AND INSIDE THE VM, THE HARNESS KEEPS ENFORCING Egress: allowlist proxy only — metadata IPs blocked, fail-closed in prod Secrets: kernel-attested vends, 300 s TTL · Exec: token-gated Isolation classes — in-VM harness enforcement The fail-closed gate — choose_backend + build_job Refusal semantics — missing hardened class means the workload does not run runc — trusted only gVisor — standard and untrusted Kata — hostile and below Firecracker — session-scoped VMs + in-guest tool runner Wasmtime — capability-scoped wasm sandbox
You declare how much you trust the code — the runtime either honors it or refuses. There is no third option.

The decision tree

Pick the least-privileged class the workload can tolerate. Walk it top to bottom and stop at the first match:

Is the code first-party and signed?                 ── yes ─▶  TRUSTED   (runc)
   │ no
Does it load packages from the internet,
or drive a browser, or run user/LLM-generated code? ── yes ─▶  UNTRUSTED (gVisor + egress-deny)
   │ no
Is the *input* adversarial / hostile?               ── yes ─▶  HOSTILE   (Kata microVM)
   │ no
                                                    ────────▶  STANDARD  (gVisor, default)

The classes

ClassRuntimeClassUse when
trustedruncSigned first-party code only. Runs on shared nodes. No untrusted package loading, no LLM-generated code path.
standardgvisorDefault. Your own code on shared, gVisor-isolated nodes. Used when the class is left unset.
untrustedgvisor + egress-denyLoads internet packages (PyPI/npm), drives a browser, or runs LLM-generated code. Adds an egress allowlist and seccomp deny-default.
hostilekata-qemu (or kata-fc)Adversarial workloads. Full-kernel microVM via Kubernetes on a dedicated node pool with no co-tenancy.
wasmcrun+wasmWebAssembly workloads (or in-process Wasmtime on trusted hosts).
devcontainergvisorLong-lived workspace: a persistent pod + PVC that survives across calls.

Defaults & forced classes

  • An unset class resolves to standard.
  • Marketplace bundles and LLM-generated bundles are forced to untrusted — they cannot opt down.
  • trusted requires the signing key; a bundle without it cannot claim trusted.

The fail-closed gate

This is the load-bearing invariant. Untrusted and hostile code never runs in a bare pod:

  • A node may satisfy untrusted only if it advertises the gvisor RuntimeClass.
  • A node may satisfy hostile only if it advertises kata-qemu / kata-fc.
  • A node that lacks the required hardened RuntimeClass fails closed — the run is refused. It is never downgraded to runc.

The requirement is enforced in two places: the manager (which builds the pod spec) and the scheduler's node-capability filter (which only places onto nodes advertising the needed RuntimeClass).

Important: If your cluster has not provisioned the gvisor / kata RuntimeClasses, untrusted and hostile workloads are refused — correct, but operationally visible. The data-plane installer provisions them and a preflight check surfaces the gap before traffic.
Note: gvisor intercepts syscalls in user space — a minority of workloads hit an unimplemented syscall. Declare hostile (full kernel via Kata) for those, or get a runc-on-trusted exception via the admin override.

In a spec

Demo 02 (web-scraper) declares untrustedbecause 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/s

Egress enforcement and secret vending for untrusted workloads are covered in Identity & secrets.