Isolation options for running untrusted agent code on Apple Silicon, determinism controls, SARIF-emitting analyzers, and the CodeQL licensing blocker.
Sandboxed execution and static analysis
Verified 2026-08-31. See tooling-decisions.md for the cross-category summary.
Sandboxed execution is the mechanism CodeVetter’s verdicts rest on: if the isolation is not reproducible, the evidence is not either. This page covers isolation on Apple Silicon, the determinism controls that make runs comparable, and static analyzers that emit SARIF.
Recommended sequence: prove the contract with Apple container, then embed
On a supported host, qualify the official Apple container CLI first. It gives
the team a maintained, OCI-compatible lightweight-VM implementation with which
to measure containment, mount policy, networking, resource bounds, startup,
and teardown before accepting an in-process VMM dependency. The signed 1.3.1
package is now qualified on the Apple Silicon macOS 27 trial host. A cached
no-op container started in 0.61 seconds; read-only root/workspace, bounded CPU
and memory, an internal no-DNS network, dropped capabilities, host-environment
absence, and teardown all behaved as expected. The first image/init-image run
took 20.56 seconds and the cached images occupied 1.45 GB.
One native contract failed: the CLI accepted a controlled bind source containing ..
when it resolved outside the intended fixture root. CodeVetter must canonicalize
and enforce workspace containment itself; Apple Container’s mount validation is
not that policy. The Rust mount planner now rejects traversal, escaping
symlinks, source replacement, mount-string injection, and malformed guest
targets, then revalidates the source identity immediately before returning a
read-only bind argument. The qualification receipt
records identities, measurements, teardown, and the remaining runner gates.
The selected first adapter is the external Apple CLI on supported Macs. It is already signed and measured, keeps the app bundle free of a VMM/FFI dependency, and does not add nested signing work. Installation and service startup remain explicit owner actions. A product runner must still prove exact-version and local-image preflight, an attested internal network, minimal environment, timeout/cancellation, bounded output, and cleanup before this becomes a shipped isolation claim.
The string-based CLI retains a final TOCTOU window after source revalidation.
That boundary is acceptable only for CodeVetter-owned immutable worktrees that
the untrusted guest cannot mutate before launch. If concurrently mutable host
roots become a requirement, move to Apple’s Containerization Swift package and
an audited descriptor-based mount path. Keep libkrun as the fallback if real
workloads disprove the first-party path, rather than taking on FFI now.
libkrun (Apache-2.0, containers/libkrun, 2,643★) is a small VMM
library written in Rust and built on Apple’s Hypervisor.framework. It is
not Docker, not a daemon, and not a subprocess — it links into the existing Rust
backend via its C API, boots a minimal Linux microVM per verification run, and
tears it down after. That matches the local-first, no-server constraint without
requiring the user to install and license a GUI application.
apple/containerization (Apache-2.0, Swift, 8.9k★) is the library beneath
that first-party CLI and is the follow-on once a Swift sidecar is acceptable.
It claims sub-second boot and is the only option here with native VM state
save/restore — the actual warm-start mechanism. It requires macOS 26 and
Apple Silicon.
Colima + Lima (MIT / Apache-2.0) is the pragmatic fallback for pre-macOS-26 or Intel machines: a one-time Homebrew install rather than a bundled component.
Comparison
| Option | License | Daemon? | Bundleable? | macOS ARM? |
|---|---|---|---|---|
| libkrun | Apache-2.0 | No — in-process C API | Yes, as a Rust dependency | Yes, explicit HVF backend |
Apple container CLI |
Apache-2.0 | Yes, system service | External prerequisite | Apple Silicon, macOS 26+ |
| apple/containerization | Apache-2.0 | No at library level | Via a Swift sidecar | Apple Silicon, macOS 26+ |
| Colima + Lima | MIT / Apache-2.0 | CLI-managed VM, no GUI daemon | Prerequisite only | Yes (vz driver, default since Lima v1.0) |
| Podman | Apache-2.0 | podman machine VM |
Prerequisite only | Yes |
| Docker Desktop | Engine Apache-2.0; Desktop app proprietary | Yes, GUI app + background VM | No | Yes, but license-gated |
| Firecracker | Apache-2.0 | — | — | No — KVM only |
| gVisor | Apache-2.0 | — | — | No — Linux platforms only |
| Wasmtime / WasmEdge | Apache-2.0 | No | Yes, trivially | Yes |
Three findings that overturn common assumptions
- Firecracker and gVisor do not run on macOS at all. Firecracker’s own production-host docs state it relies on KVM, which is Linux-only. gVisor’s platform docs list KVM, Systrap and Ptrace — all Linux-specific. There is no ARM-macOS host mode for either. Any plan built around them is dead on arrival.
- Docker Desktop is a licensing gate, not just an install burden. The engine (moby) is Apache-2.0, but the Desktop app is proprietary and metered: free only under 250 employees and under $10M revenue. That becomes a legal problem the moment the company crosses either threshold.
- WASM cannot be the primary sandbox. It is fast and trivially embeddable, but only runs workloads compiled to WASI. CodeVetter’s actual core workload is TypeScript/Node web tasks with real browser and API behaviour. Keep WASM for pure-computation graders only.
Determinism controls
These are mostly guest/application-layer concerns, so they transfer across whichever isolation option is chosen:
| Control | Mechanism |
|---|---|
| Frozen clock | libfaketime via LD_PRELOAD inside the guest |
| Seeded RNG | Runtime-level shims, not a hypervisor feature |
| Network isolation | --network none or equivalent on all container/VM options |
| Filesystem rollback | Copy-on-write layers (recreate per run, not a memory snapshot) |
| CPU/memory limits | cgroups-equivalent flags; WASM adds fuel/instruction limits |
| VM state save/restore | VZ/HVF family only — see below |
Warm start is the strongest argument for the VZ/HVF family. True memory-state
snapshotting is confirmed only for Virtualization.framework-based options.
Lima’s vz: implement auto save/restore (PR #2900)
measured 37s → 13s boot-to-ready, and notes saveOnStop requires macOS 14+
and is arm64-only. Docker’s and containerd’s copy-on-write layer model does not
give you this.
Rust crates for driving isolation in-process
libkrun— the VMM itself, Rust, Apache-2.0, consumable viabindgenFFI. The most direct no-shell-out path.bollard— async Docker Engine API client, Apache-2.0, v0.21.1 (2026-08-16). Avoids shelling out to thedockerCLI but still needs a running daemon.testcontainers(Rust) — MIT/Apache-2.0, v0.28.0. Higher-level, same daemon prerequisite.- Direct Rust bindings to
Virtualization.frameworkexist (virtualization-rs,apple-virtualization,virt-fwk) but are all low-star, thinly maintained side projects. Do not depend on them.
Static analysis with SARIF
| Tool | License | Offline | Native SARIF | Verdict |
|---|---|---|---|---|
| Biome | Apache-2.0 | Yes | Verified SARIF 2.1.0 | Wired for repository evidence |
| ast-grep | MIT | Yes | Envelope invalid in 0.45.2 | Structural matching is sound, but do not upload its native SARIF |
| Ruff | MIT | Yes | Yes (--output-format=sarif) |
Python scope only |
| Semgrep | CLI is LGPL-2.1 | Yes | Yes | Engine fine; rules are the problem |
| Clippy | Apache-2.0 | Yes | No | Needs clippy-sarif converter |
| ESLint | MIT | Yes | No | Third-party formatter |
| CodeQL | Custom | Yes | Yes | Repository-only; 🚫 customer-code product use |
🚫 CodeQL is a hard blocker for this product
The CLI license prohibits use “in connection with any codebase that is not an Open Source Codebase” and prohibits automated analysis, CI or CD on such code, absent a paid GitHub Advanced Security agreement.
CodeVetter’s entire purpose is running against users’ private repositories. CodeQL therefore cannot be a default engine unless every user separately holds a commercial GHAS license. This is a legal blocker, not a preference.
⚠️ Semgrep — the engine and the rules have different licenses
The CLI is LGPL-2.1 and runs offline. But the registry rulesets (p/default,
p/security-audit, …) are under a separate Semgrep Rules License v1.0:
“You may use the rules only for your own internal business purposes. This
license does not allow you to distribute the rules, or to make them available to
others as a service.”
Running Semgrep against a user’s own code is internal use and fine. Bundling a vendored copy of the registry rules into every install is the gray area — that needs counsel before shipping, and is not cleared by the engine’s LGPL alone.
Biome’s SARIF path is now verified and wired for the public repository, so no
second generic linter is justified without a missing-rule case. A focused
ast-grep 0.45.2 trial found correct structural locations but an invalid SARIF
root: version contains the tool version rather than 2.1.0, and $schema is
absent. The tracked receipt
keeps this a measured rejection instead of an assumed capability.
Observability — do not add a dependency
Use the existing SQLite database. Cost, latency and tokens per run are a
handful of columns on the existing run/verification tables: cost_usd,
latency_ms, tokens_in, tokens_out, provider, model.
- OpenTelemetry Rust (Apache-2.0) is well-maintained but built for exporting to a collector. Running it in-process purely to write rows you could write directly is dependency accumulation.
- Langfuse core is MIT and self-hostable, but it is a server product (Postgres + web app) — a flat mismatch with “Tauri desktop binary, no server.”
Revisit OTel only if CodeVetter grows a genuine multi-process or remote-agent topology needing distributed tracing.
Open questions
Flagged UNVERIFIED and worth closing before committing engineering time:
- The
Virtualization.frameworkentitlement question. Apple’s docs are JS-rendered and could not be fetched. Circumstantial evidence is strong — Lima, Colima andapple/containerall ship as notarized, non-App-Store binaries usingvzwithout incident, supporting the common understanding thatcom.apple.security.virtualizationis required only for sandboxed Mac App Store apps, not Developer-ID-signed builds. Confirm directly against Apple before relying on it. libkruncold-boot time on Apple Silicon. “Smallest possible boot time” is a stated design goal; no published benchmark was found.- Podman’s default macOS backend (applehv vs libkrun vs QEMU) and its exact network/CPU/memory flags.