Security
Threat Model
Everything inside a range is assumed hostile: the attacker agent by design, targets once compromised. Containment is correspondingly strict, protecting two things that live outside the sample: the machine and credentials beyond the range, and the integrity of the sample’s score. The model assumes one sample per host instance, for the instance’s lifetime: a compromised host can persist across VM and container teardown, so a used host is never returned to a pool (the lease contract encodes this as release-is-destruction).
Escapes are treated as possible but unprofitable. An agent that escapes its VM gains an unprivileged, credential-free container; one that escapes that gains a dedicated instance holding no secrets and a sample that is simply discarded. Evidence streams off the instance continuously, so the record received before an escape cannot be rewritten afterward (see Evidence).
Three Nested Boundaries
The agent sits behind three boundaries, each independent of the last:
- The VM boundary. Every guest, including the attack box, is a hardware virtual machine. The escape surface is kept small and declared: virtual hardware is compiled explicitly per role, and the attack box sees only virtio disk, network, vsock, serial console, and entropy devices.
- The range container. Nearly all historical VM escapes break out through the device-emulation process and land here: an unprivileged container holding no credentials, with Docker’s default capabilities dropped (eight at the floor:
NET_ADMINplus the file-ownership and user-switching set that lets the VMMs run as non-root users; plans serving DHCP or search domains addNET_BIND_SERVICE, and DHCP addsNET_RAW), four device nodes (/dev/kvm,/dev/net/tun,/dev/vhost-net,/dev/vhost-vsock), and the standard seccomp and AppArmor profiles. The range’s bridges and firewall rules exist only inside this container’s network namespace, and teardown is the container lifecycle. - The instance. Reaching the host from the container requires a second, unrelated exploit, and the host is a dedicated machine: even full compromise yields only the sample context, with the cloud hypervisor between it and everything else.
Per-Guest Identity
An escape from one guest’s VMM should own nothing else, so no two VMMs share an identity:
- QEMU guests each run as their own unprivileged uid, derived from the guest’s leased vsock CID, with QEMU’s syscall sandbox on and the container’s AppArmor profile around the whole stack.
- The attack box defaults to Cloud Hypervisor, a smaller rust-vmm device model with no legacy hardware surface, started directly (no management daemon in its path) as its own unprivileged per-guest uid with no capabilities and Cloud Hypervisor’s default seccomp filter. The binary and its firmware are sha256-pinned into the range image.
Boundary strength orders rust-vmm above QEMU/KVM, which has a long device-emulation CVE history. The Cloud Hypervisor attack box is a gate, not a tier upgrade: compromising targets is the sanctioned objective, so a successful agent reaches QEMU through any rooted target regardless of what the attack box runs on. It closes the quiet path: an escape launched from the one guest where the agent has root and full tooling from the first prompt. Escape attempts are forced through observable, evidence-producing compromise work first.
Virtual Machine Monitors
Hosts and routers run under QEMU via libvirt by default; the dedicated attack box defaults to Cloud Hypervisor (declare attacker.vmm: qemu to keep QEMU), and any host or router may opt in with vmm: cloud-hypervisor. Each Cloud Hypervisor VMM gets the same per-guest uid treatment as the attack box (Per-Guest Identity); none shares QEMU’s libvirt-qemu.
Cloud Hypervisor guests have no AF_VSOCK listener on the host: the control plane reaches them through the VMM’s hybrid-vsock Unix socket (Cloud Hypervisor’s vsock transport: a host-side Unix socket the VMM bridges to the guest’s vsock device), and every dialer checks the socket peer’s credentials (SO_PEERCRED) against the VMM’s uid before sending a byte, refusing anything else without retry. The sockets live in a private per-user host directory that up prepares and down removes.
Two planning refusals bound the opt-in: a Cloud Hypervisor guest cannot carry guest content yet (cloud-hypervisor-content-unsupported: the build captures provisioned guests through libvirt, which cannot see CH guests), and a pinned cpu_model refuses (cloud-hypervisor-cpu-model: CH guests always see the host CPU).
Control Plane
Guest control rides virtio-vsock, not a management network. The rules:
- Guests get lab-segment NICs only: no management NIC, no management subnet for in-range software to discover, firewall, or pivot through.
- The plane is strictly connect-from-host: the in-guest daemon accepts connections only from the host, and no host-side
AF_VSOCKlisteners exist for a compromised guest to dial. - Cloud Hypervisor guests are reached over the VMM’s hybrid-vsock Unix socket with the peer-credential check described in Virtual Machine Monitors.
- Everything received over the channel comes from guests the agent may have compromised and is treated as untrusted input: strict schema validation, reader-side byte caps, and tamper verdicts for protocol violations an honest endpoint cannot produce (see the retry contract).
- The exec witness is guest-tier evidence under the same rule: the pre-compromise exec timeline is immutable once streamed, a root attacker can forge or starve it from the moment the guest falls (visibly, never silently), and nothing scores over it.
In the other direction, in-range defenders can detect everything the agent transmits (that is realism, not a leak), but they cannot observe the agent’s interior, connect to it (standard images ship zero network listeners), or reach the channel controlling it.
Network Containment
Scenario networking and operator containment are different authorities, each with its own owner:
- Scenario intent (the definition):
modeandegressdescribe the scenario, and the compiler realizes them in guest configs and router ACLs. - Mutable in-range state (run time): routers are ordinary guests and their firewall rulesets are scenario state. Compromising a router and rewriting its rules is legitimate agent behavior, not a containment event, so nothing operator-relevant may depend on in-guest rules.
- Deployment-enforced external containment (the authority that survives compromise): hypervisor-side firewall invariants in the range’s network namespace, outside every guest, plus the instance’s own network policy. These are compiled from deployment policy, not from the scenario: a definition may request external capabilities, and the deployment grants or rejects them. No schema field weakens operator containment.
Proving Containment
A range that carries behavioral checks proves its containment at build time. Before the checks, the proof boot (the build’s second boot of the captured images, where the checks run; see Behavioral Checks) runs the conformance stage: a shared-segment target TCP-sweeps the attacker expecting zero open ports, and the attacker verifies that no undeclared management endpoint answers on its segments. Any finding refuses the manifest exactly like a failed check, so an uncontained range is never consumable.
The stage’s scope limits: the sweep is TCP only (ports 1-1024 plus a high-port sample), foothold ranges skip it (the agent lands on a declared host whose services legitimately answer), and a range without behavioral checks has no proof boot, so it gets no conformance stage either.
External Attack Boxes
A deployment may substitute an externally-managed VM for the dedicated attack box (Attack Box). The substitution preserves the structure on this page: the attachment is a tap built inside the range container’s network namespace with its fd handed to the external VMM, so the host netns still carries zero range state, and bridge-side evidence capture still sees every frame the box sends. What changes is whose containment stack holds the agent’s seat: the range is then only as contained as the external stack, with the conformance battery as the proven floor (surface sweep, exec parity, lifecycle) rather than this page’s architecture. The box’s missing console stream is recorded as a declared evidence gap, and inspect-ranges doctor carries a standing warning while the provider is configured.
Score Integrity
Streaming preserves the evidence record but does not by itself detect an escape, so each goal type is scored against proof the agent cannot fabricate, and detection-type goals consume coverage watermarks (the per-poll records that make telemetry coverage decidable; see Evidence) rather than trusting exported content (see Challenges and Evidence). An escaped agent can invalidate its sample; it cannot silently falsify it. Score integrity rests on that detection, not on the boundaries being unbreakable.
Topologies and Credentials
The co-resident topology (the local backend) puts the driver, with its model API credentials, on the same host as the range: it is for development and trusted runs, and its credentials must be scoped and revocable (per-run model keys, no long-lived cloud credentials). The separated topology (uds:) isolates the process that parses attacker-influenced bytes from the process holding credentials and is the preferred configuration where the agent is genuinely untrusted; see Range Host.
Both v1 backends record isolation="shared" in the eval log (Range Host), so the one-sample-per-instance posture above is a deployment property, not something v1 enforces.
For untrusted-model runs, point the eval log directory or INSPECT_RANGES_EVIDENCE_SINK at off-instance storage, since instance-local evidence does not survive instance compromise (deployment guidance); inspect-ranges doctor warns when evidence resolves to local storage.
