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 on every PRsupply chaincargo-deny + cargo-audit jobs; 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-security and the guest seccomp.json manifest. 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 β€” they gate what is allowed to run before any backend is chosen, and hold identically across all of them.

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.
BrowserWasi (browser tier)β€”βŒβŒβŒβŒClaim-free β€” no numbered claims. The browser’s sandbox/process isolation is the only boundary; there is no hypervisor, no guest kernel, no vsock, 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 BrowserWasi backend runs workloads inside the browser’s own WebAssembly engine. It has no hypervisor boundary and runs the guest workload directly as a WASI module. 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:

  • Runs the workload as a WASI Preview 1 module inside the browser’s WebAssembly engine
  • Has no Linux kernel, no initramfs, no vsock, and no virtual devices
  • Enforces isolation through the WASI capability model (preopened directories, host imports)
  • Mediates network egress through browser-native fetch() via the mvm:egress host import
  • Is never auto-selected and only available through explicit --hypervisor browser-wasm
  • Is for demos, playgrounds, and browser-local development only
  • Does not apply to production workloads

When a browser-tier backend is selected, the same admission, policy, and audit semantics that apply to host backends are enforced inside the browser’s own WebAssembly engine.

  • 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 (no container fallback on any default path β€” only the explicitly selected ADR-034 dev tier). 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 (TPM/SEV/etc.) is out of scope for v1.

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