Skip to content

Security

The Matryoshka model: how mvm isolates untrusted code

mvm runs untrusted Linux workloads in microVMs. This page explains the five trust layers, the CI-enforced security claims, and which claims hold for each backend.

mvm’s job is to let you run untrusted code β€” third-party software, AI-generated scripts, CI runners, sandbox workloads β€” and trust the isolation. This page explains the security model in one diagram and one matrix.

For the ordinary command β€” mvmctl machine run --image ... -- ... β€” mvm starts from a deny-by-default posture. The exact enforcement tier is printed by mvmctl doctor and in launch receipts, but the user-visible contract is:

  • A real microVM boundary: each workload gets its own guest memory, virtual devices, and Linux kernel. A container is never silently substituted.
  • A private filesystem: the image and the workload’s ephemeral writable layer are visible inside the guest. Host directories are absent unless you explicitly mount them; mounts are a deliberate part of the launch plan.
  • No network by default: there is no egress until you enable networking and admit destinations with --allow-host. Inbound access is not implied by an outbound rule.
  • No raw host secrets in the guest: secret-aware egress substitutes credentials at the host boundary, so the workload receives only the placeholder or response it is authorized to use.
  • An auditable launch decision: the image, resource limits, mounts, network posture, admission profile, and backend are captured in the signed execution plan before boot.

These defaults are intentionally stronger than β€œa VM with a public network.” They make the safe path the shortest path while keeping explicit escape hatches visible in the command line and the receipt.

mvm is designed to bound an untrusted guest that tries to escape to the host, read another workload’s state, use an undeclared host mount, reach private host services, or obtain a credential that was not released for its destination. The guest-to-host control channel is host-brokered and typed; it is not SSH and does not require an inbound guest listener.

mvm trusts the host operating system, the selected hypervisor, and the release or source artifacts used to build it. A compromised host or hypervisor is outside this boundary. A workload can also misuse a destination, mount, secret, or capability that its operator explicitly allowed. Multi-tenant sharing inside one guest and hardware-backed remote attestation are not default guarantees.

Use the strictest profile that fits the workload, keep networking disabled when it is unnecessary, allowlist only the destinations required, prefer read-only mounts, pin production images by digest, set resource and lifetime limits, and keep the host patched. The security posture is visible and enforceable, but it cannot infer whether an explicitly granted capability is safe for your code.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ L5 β€” Workload (your untrusted code) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ L4 β€” Guest agent (parses host messages, launches code) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ L3 β€” Guest kernel (Linux, ephemeral, isolated) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ L2 β€” VMM (Firecracker, Rust, seccomp-jailed) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ L1 β€” Host + hypervisor (KVM / HVF) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Each layer trusts only the layer below it. An attacker has to break through every boundary above to reach the host. A failure in any one layer is bounded β€” the layer below still enforces its own contract.

This pattern (sometimes called the matryoshka model after the nested Russian dolls) is the same defense-in-depth used across the production microVM / hardened-isolation ecosystem. mvm’s adaptation is that L5 is enforced inside the guest β€” even a guest-kernel compromise doesn’t give arbitrary access to other in-guest services. See ADR-001 for the full decision record.

mvm makes fifteen CI-enforced security claims, plus three preview claims whose witnesses run but whose guarantee is narrower. Each one is backed by a test or a continuous-integration check that fails the build if the claim ceases to hold. The claims that defend a nesting layer are what the layer model is about; the rest defend the supply chain and the admission path, which decide what is allowed to run inside those layers at all.

#ClaimDefends layerHow it’s enforced
1No host-fs access from a guest beyond explicit sharesL2 / L5Per-service uid + seccomp standard default + setpriv bounding-set drop; user-volume allow-list defaulting to read-only
2No guest binary can elevate to uid 0L2 / L4setpriv --no-new-privs in launch path; /etc/{passwd,group} are read-only bind-mounts
3A tampered rootfs ext4 fails to bootL3dm-verity sidecar + roothash on cmdline + a verity-aware initramfs owning the boot pivot
4A production-safe run cannot invoke DevOnly guest-agent verbsL4Runtime profile + signed VerbGrant intersection; grant and conformance tests enforce the full DevOnly set
5Vsock framing, supervisor-config JSON, and the datapath ingress are fuzzedL2 / L4cargo-fuzz targets over the host↔guest messages, the supervisor config parser, and the userspace datapath ingress; deny_unknown_fields on every type
6Pre-built dev image is hash-verifiedsupply chainSHA-256 manifest streamed through the download; a mismatch rejects and deletes it
7Cargo deps are audited nightly and on every release tagsupply chaincargo-deny + cargo-audit jobs in security.yml; reproducibility double-build
8Every workload runs from a signed, audited ExecutionPlanadmissionEd25519 host-signer keypair; validity window + nonce replay-store; chain-signed admission entries
9Every published bundle is content-addressed and key_id-pinnedsupply chainA rejection ladder at fetch and at admit time: unknown key, tampered manifest, key_id mismatch, unsafe path, pin drift
10No untrusted workload reaches the network unless policy admits itdata containmentPolicy defaults to deny-all; the workload guest has no NIC, so egress leaves only over vsock to a host endpoint that authorizes it
11Every application-dependency volume is sealed and auditedsupply chain (app layer)A hash-locked volume carrying an SBOM, a CVE scan, and a hash-chained manifest; admission refuses a tampered volume
12Every broker service is bound to a signed plan bindingadmissionBinding-gated dispatch, enforced before the handler runs, with a rejection ladder for unbound and out-of-profile calls
13No raw secret value crosses the broker channeldata containmentDestination-bound, time-bound signed credentials only; raw secret bytes never leave the supervisor’s address space
14Every OCI image admission records provenance in the audit logsupply chainA provenance entry carries registry, repo, resolved digest, layer digests, and trust verdict; production refuses a mutable reference
15A sealed production microVM has no shell, no DevOnly verbs, no PTYL4Only the dev /init serves a console; console capture is write-only with no host input; the host gate refuses console on a sealed image

Three further claims β€” 16 (egress substitution keeps a raw secret off the guest), 17 (workload stdin is grant-gated and secret-scanned), and 18 (workload resource bounding) β€” are preview. Their witnesses run in CI, but each carries a limits note in ADR-001 that has to be read before treating it as enforced. See CI-enforced security claims.

L1 (host + hypervisor) doesn’t carry its own claim β€” the host is trusted by definition. If your host is compromised, every layer falls. Locking down the host (firewall, package hygiene, full-disk encryption) is your responsibility.

Every workload also goes through a signed admission step before boot. mvmctl machine run synthesizes an ExecutionPlan, signs it with the host key, checks its validity window and replay nonce, then emits a chain-signed audit entry.

The plan now carries an admission_profile: a compact record of the workload’s declared intent and the controls selected for that intent:

  • intent, for example vm:boot, code:execute, or agent:web-research
  • seccomp tier selected for the run
  • network, filesystem, egress, and tool policy refs
  • secret-release posture (none, plan-bound, or attestation-bound)
  • audit taxonomy and required labels

This does not add a second seccomp implementation or new execution capability inside the sandbox. Runtime syscall filtering still comes from mvm-runtime’s security/seccomp.rs filter selection and the mvm_core::crypto::seccomp::SeccompTier syscall tiers. The admission profile records the selected tier in the signed plan so the audit chain can prove which security posture the workload was admitted under.

mvm runs on multiple backends. Not all backends carry every claim. The tier you actually get depends on which backend mvm picks for your run.

The columns below are the nesting layers, so this matrix covers the claims that defend a layer. The supply-chain and admission claims (6, 7, 8, 9, 11, 12, 14) are backend-independent across the claim-bearing microVM backends β€” Firecracker, HVF, and libkrun β€” where they gate what is allowed to run before a backend is chosen. They do not extend to every row below: QEMU is type-excluded from the admitted workload path, and the browser tier is claim-free and asserts none of the numbered claims. Both exclusions are restated in the rows themselves.

BackendL1L2L3L4L5Tier
Firecracker (Linux + KVM)βœ…βœ…βœ…βœ…βœ…Tier 1 β€” full ADR-001. Every layer-defending claim holds.
HVF (macOS 26+ Apple Silicon β€” auto-default)βœ…βœ…βš οΈβœ…βœ…Tier 2 β€” claim 3 (verified boot) partial; Hypervisor.framework, vsock-only egress (no guest NIC). The macOS-26 auto-default.
libkrun (Linux KVM, macOS Apple Silicon HVF)βœ…βœ…βš οΈβœ…βœ…Tier 2 β€” same as HVF.
QEMU (Linux KVM/TCG)βœ…βš οΈβš οΈβœ…βœ…Tier 2 β€” claim 3 partial; QEMU’s larger device model raises L2 audit cost. Deliberately outside claim 10’s egress enforcement, because it carries no untrusted multi-tenant workload. Dev/test only (--hypervisor qemu; the no-/dev/kvm path via TCG software emulation). Never selected by mvmd.
WebLinux (browser tier)β€”βŒβŒβŒβŒClaim-free β€” no numbered claims. A real Nix-built Linux kernel boots under QEMU-Wasm, but the browser’s sandbox and process isolation are the only boundary: there is no hypervisor and no verified boot.

βœ… = layer fully enforced. ⚠️ = layer partial (named exception). ❌ = layer collapsed (claim does not apply). β€” = not applicable.

On every default and production path, mvm has no Tier 3 and no container/Docker fallback. A shared-kernel container is not a microVM: its isolation comes from the host kernel’s namespace and cgroup machinery, which is shared with the host. In 2024–2025 the container ecosystem produced multiple CVEs (Leaky Vessels, NVIDIAScape, runc race conditions, Docker Desktop priv-esc, runc masked-path) that yielded host escape from inside a container β€” none of which matter inside a microVM, where the guest kernel is isolated by hardware. If a host has no microVM-capable backend, mvm does not silently drop to a weaker boundary; it fails closed.

There is no carve-out. ADR-034 has been retired and the Docker dev-tier backend removed (Plan 329). A host without a usable microVM backend fails closed; mvm does not offer a shared-kernel container path on any default, production, or explicitly selected runtime path.

The WebLinux backend runs workloads inside the browser’s own WebAssembly engine. It has no hypervisor boundary: a real Linux kernel boots, but under QEMU-Wasm rather than on hardware virtualization. This makes it a claim-free tier: it cannot assert any of the numbered security claims because there is no hardware isolation.

The browser-tier backend:

  • Boots a real Nix-built Linux kernel under QEMU-Wasm inside a browser Worker
  • Has no hardware isolation boundary β€” the browser sandbox and process boundary are the only ones
  • Is never auto-selected and only available through explicit --hypervisor web-linux
  • Is for demos, playgrounds, and browser-local development only
  • Does not apply to production workloads

On a native host the backend is a fail-closed stub: it is selectable so the catalog and CLI help can list it, but every lifecycle call refuses and it is barred from carrying an untrusted workload.

Separately, the host-wasmtime wasm tier runs a user-supplied WASI Preview 1 module directly with no Linux kernel at all. It is also claim-free and opt-in only, and its mvm:egress host import relays each request over a Unix socket to the same host-side substitution endpoint the vsock-backed tiers use β€” not to a browser fetch().

  • Production / untrusted code β†’ Tier 1. Linux + KVM + Firecracker. No exceptions.
  • macOS dev or CI on Apple Silicon β†’ Tier 2 (HVF or libkrun). Verified boot is the open item.
  • Linux dev/test without /dev/kvm β†’ Tier 2 QEMU (--hypervisor qemu, TCG software emulation). A real microVM, slower; dev/test only.
  • macOS Intel / native Windows β†’ unsupported for local microVM isolation today. There is no container fallback on any path: ADR-034 is retired, the Docker backend is removed, and --hypervisor docker is hard-refused. The one opt-in container-kernel tier is apple-container, which boots Apple’s prebuilt container kernel on the in-house HVF VMM β€” a microVM, not a shared-kernel container. WSL2 with nested /dev/kvm is the supported Windows-adjacent libkrun workload path; a Hyper-V managed Linux builder remains future backend work.

mvmctl doctor reports your current tier on the running host.

ADR-001 names three explicit non-goals so we don’t accidentally commit to defending against them:

  • A malicious host. mvm trusts the host with the hypervisor and the build keys. If your laptop or your server is compromised, every layer falls.
  • Multi-tenant guests. One guest = one workload. Sharing a single guest VM between mutually-distrusting tenants is out of scope.
  • Hardware-backed key attestation against a malicious host. An opt-in TPM2 provider ships (the attestation-tpm2 feature), and its measured-boot quotes are a real host-measured attestation input. What it does not do is move the trusted-host boundary: the host still owns the TPM, the connection to it, and the launch material, so a compromised host stays out of scope. Real separation needs confidential-computing hardware (SEV-SNP/TDX), which no shipped backend targets.

If your threat model needs any of those, mvm is not the right tool today. ADR-001 documents these limits explicitly.