Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Security qualification

What Q1 (2026-10-08) examined in the implementation as it stood before Gate 2 widens it, what it found, what it fixed, and what it carries. Findings are classified by docs/quality-model.md: technical severity and programme disposition, never one score. Reporting is SECURITY.md; the process after a report is docs/vulnerability-process.md.

The evidence is under target/gates/q1/: six audit reports (audit-*.md), their probes (probes/), the fuzz campaign, the mutation campaign, the dependency audit and the secret scan. Starting HEAD 4189318; the fixes are commits 777ac98 onwards.

Scope

Q1 qualified the surface as it exists — the language through Gate 2’s numbers, arrays, constants, bytes and text, both backends, the runtime, the toolchain commands, packages, caches, the contained runner and the bench sandbox — not a future one. It claims nothing about arbitrary foreign code a program calls, and it does not claim implicit-flow non-interference.

Summary

Findings95: 1 CRITICAL, 10 HIGH, 21 MEDIUM, 45 LOW, 18 INFO (two pairs are one defect seen twice: Q1-A1-08 and Q1-A2-03, Q1-D-21 and Q1-FI-02)
Release-blockers found8 — all fixed (one with a stated residual, below)
Gate-blockers found7 — all fixed or decided
Valid-source miscompiles found1 (the summation rewrite, Q1-B-01) — fixed
Cache hit changing a verdict1 (Q1-B-C1) — fixed
Profile bypasses within claimed scope4 (Q1-C-01…04) — fixed
Cleanup able to delete unowned resources1 (Q1-A2-01) — fixed
Compiler crashes on malformed input1 class (UInt64 products, Q1-B-03) — fixed

Attack surface

Every process the shipped CLI starts was read (audit-a1-surface-unsafe-ffi.md, §5): clang and the linker with a cleared environment and fixed arguments, dsymutil, ar, ssh-keygen for attestation, the system sampler, and the programs nazm test, profile and accel build and then run. No command line is built by a shell from program or user data; curl in the agent bench takes credentials on standard input; the only archive reader is xtask’s, bounded and allow-listed.

Fixed: scratch directories that executables were built in and run from were predictable and adopted an existing directory or symlink — now exclusive and 0700 (Q1-A1-02, -03); a comma in a shared library’s name reached -Wl, as another linker argument (Q1-A1-06); -o could overwrite a non-entry module’s source (Q1-A1-07). Carried: the object cache directory and NAZM_TEST_DRIVER are trusted from the environment, like PATH (Q1-A1-13, INFO).

Unsafe code and the C boundary

The workspace’s Rust has no unsafe (the lint forbids it in every member); the fuzzer’s coverage callbacks are the one exception. The low-level layer is the runtime’s hand-written LLVM text and assembly — allocation, atomics, the context switch, guarded task stacks, device registers, a board’s _start — with no signal handlers (§6 of the A1 report inventories each).

Fixed: generated code asked for no stack probes, so a frame larger than a task stack’s one guard page could step over it into another task’s stack (Q1-A1-01, the release-blocker); both backends now probe, and the overrun faults on the guard. The guard page’s mprotect results were ignored (Q1-A1-10). Carried, fix before v1: such a frame faults rather than failing with N0408; an export called on a small C thread faults likewise (Q1-A1-04); wasm32 passes a handle as i64 (Q1-A1-05). C’s own behaviour once called is outside every claim (Q1-A1-16).

Robustness

The fuzz campaign (contained, target/gates/q1/fuzz-contained.log): five targets — front, lower, run, lir, manifest — 300 s each, 3.40 million executions, no crash; 36 inputs to run blocked past two seconds and were counted, not kept (Q1-F-01). Malformed input by hand (audit-b-compiler.md): truncated source, extreme nesting, 10 MB identifiers, 100,000-operator chains, malformed generics, eleven malformed JSON baselines, corrupt and swapped cache entries and LSP frames — refused cleanly, except: a UInt64 product past i128 panicked the checker, the interpreter and the LIR oracle (Q1-B-03, fixed); doubly nested records made type depth exponential (Q1-A1-09, fixed); two symlinks back to a package’s root hung resolution (Q1-A2-03, fixed). Carried: huge diagnostics, quadratic checking of very wide records (Q1-B-09, -10), a huge LSP frame aborting in the lsp-server dependency.

Compiler correctness

Differential execution over the compatibility corpus, the examples, 37 numeric edge programs, 14 semantic probes, generated integer and float programs and 1,010 float values, through the interpreter, the LIR oracle, LLVM at -O0 and -O2 and Cranelift. Emitted IR carries no nsw, nuw, inbounds or fast-math flags; overflow is an intrinsic and a branch.

Found and fixed: a miscompile — the summation rewrite read the counter’s start from its last top-level = 0 and missed a later write in a nested if or loop, so native code summed from 0 (Q1-B-01, CRITICAL); float text broke an exact tie between two equally short digit strings away from even in the interpreter only (Q1-B-02); the specification contradicted itself on function values crossing into tasks and on capturing capabilities (Q1-B-04, corrected to the checker). Carried: exit_with outside 0..255 (Q1-B-05), exponential generic symbol names (Q1-B-06), the LIR oracle’s missing float formatting (Q1-B-07).

Memory and concurrency

Memory holds (audit-b2-memory-concurrency-cache.md): MIR’s ownership check rejects any function owning a value at a return or failure exit; 24 probes — traps holding Bytes and Text, views outliving owners, overlapping copies, closures in vectors, early returns from loops, out-of-memory, task failure holding values, attempts past N0616 — ended with no live value on every engine and nothing under macOS’s malloc checks. Concurrency holds as documented after about 2,000 probe runs; one defect fixed — a pool task’s select deadline past i64 nanoseconds wrapped into the past and spun a core (Q1-M-11). Carried: the wall-clock deadline (Q1-M-12), a blocking built-in holding a worker (Q1-M-13), a failed task with a blocked sibling hanging silently (Q1-M-14, documented).

Cache and incremental checking

The native object cache is sound: its key covers what decides the object, and every damaged entry was recomputed. The check cache had two defects and a blind test: an entry’s provenance facts were used unchecked, so an edited sink list turned a refusal into ok (Q1-M-07); a module making a closure filed an entry that cannot be read back, so the next run refused what a cold check accepts (Q1-B-C1); and the cold/warm comparison read diagnostic fields that do not exist, so it compared codes only (Q1-M-09). All three fixed — entries are nazm.check/5, sealed by a digest; a closure-making module files no entry; whole diagnostics are compared. Carried: a warm check names a reused module’s parameter by position (Q1-M-08); the verdict, code and spans are the same.

Profiles, capabilities, effects and provenance

253 bypass probes (audit-c-bypass.md). Effects, capabilities, task crossing and eleven profile rules held against aliases, re-exports, generics, closures, function values, trait methods in other modules, packages, cached interfaces and profile composition. Four bypasses were found and fixed: bounded-allocation missed an allocation behind any call (Q1-C-01) and every allocating built-in Gate 2 added (Q1-C-02); signed-writes missed a state built by a helper, field, array or trait method (Q1-C-03); a nazm.root beside a package’s entry dropped the manifest’s profile (Q1-C-04). Provenance: 85 explicit-flow probes through every mechanism, all refused; implicit flow is not claimed. Carried: signed-writes is syntactic.

Packages

No package construct names a program to run: manifests refuse unknown keys (now tested for build, links, [scripts], [build-dependencies]); builds call fixed host tools. The registry refuses links and tampered digests; names cannot become paths; imports stay in the canonical source root. Fixed: the symlink loop (Q1-A2-03); publish merging into a stale version directory, sealing its extra files into the version (Q1-A2-04); a lockfile naming a package twice (Q1-A2-05). Carried: path dependencies may name any directory (Q1-A2-12); lock digests only under --locked (Q1-A2-13); unlocked index writes (Q1-A2-14).

Containment

Fixed, release-blocker: the contained runner’s reaper chose containers by name and judged liveness with kill -0, which fails for a live process the user does not own — so Compose’s nazm-postgres-1 (pid 1) or another user’s run could be force-removed (Q1-A2-01). Containers now carry an owner label with their supervisor’s pid and host, and only such containers of this host whose pid ps reports gone are removed. Fixed: NAZM_CONTAINED=1 was a bare declaration — it is now honoured only under a numeric cgroup memory.max, and xtask mutate, xtask bench, fuzz/run.sh and bootstrap.sh refuse a host (Q1-A2-02); subnet overlap now compares prefix bits (Q1-A2-08). Testcontainers was not adopted (docs/integration-testing.md); its ownership law is the bench sandbox’s, whose overstated wording Q1 corrected (Q1-A2-09). Carried: the contained workload runs as root with default capabilities (Q1-A2-10, post-v1).

Supply chain

cargo deny check — advisories, bans, licences, sources — passes (target/gates/q1/deny-after.txt) against the advisory database fetched on 2026-10-08. It failed before Q1 and nothing ran it: every workspace path dependency read as a wildcard, and paste (RUSTSEC-2024-0436) is unmaintained. The first is allowed for publish = false members; the second is recorded with its path — a proc-macro of tokenizers, reached only through the agent-cost harness, absent from the shipped nazm. cargo audit reports that advisory alone. Seven crates appear at two versions (warnings, not risks). All 238 locked crates come from crates.io with checksums, and cargo xtask check’s dependency sources gate now holds that without a network. Not available here: cargo-vet, cargo-geiger, an OSV scan.

Secrets and sensitive data

gitleaks over all 671 commits: no leak. Tracked files hold no absolute home paths, no temporary paths, no chat or prompt artefacts; docs/prompts-from-chatgpt/ is untracked. review/ holds the early milestones’ review bundles, tracked deliberately (scripts/source-digest.sh), with no secret.

Failure injection

target/gates/q1/failure-injection.md: a missing or failing or killed clang, a read-only or directory output, output naming the source, SIGINT and SIGTERM mid-build — each an explicit failure with a defined status (4, or 128 + the signal) and no artefact at the output path. Interrupted contained runs, campaigns, killed harnesses and timeouts are held by the lifecycle and sandbox tests cited there. Not exercised: disk-full, an unavailable GPU or debugger.

Release and installer requirements

No installer exists; a release is a local candidate (scripts/release-candidate.sh) with a SHA-256 manifest verified on assembly, a provenance record (commit, clean tree, build-input digest over every tracked file, toolchain, target, image, bootstrap record), a CycloneDX SBOM and signed in-toto statements (nazm attest, SSH signatures with the caller’s key — Nazm holds none). The final release gate must, before anything is published:

  1. re-run the x86_64-apple-darwin corpus under Rosetta 2 that docs/stability.md cites, and the full mutation catalogue;
  2. if an installer or archive is added: refuse entries with .., absolute paths or links leaving the destination; never follow a symlink at the destination; write with explicit modes and set the executable bit only on the binaries; modify no PATH without consent; uninstall only what it installed; verify every download against the manifest’s digest before use and refuse the wrong architecture; treat a truncated download as a failure;
  3. make the repository public, enable private vulnerability reporting and confirm the fallback address (SECURITY.md, docs/vulnerability-process.md);
  4. settle licensing (docs/licensing.md) — a publication blocker until the human decision is made;
  5. decide the exit statuses of usage errors and user errors (Q1-D-06, -07) — decided and frozen in Q1-C1: 0 success, 1 invalid source/program/request, 2 run-time failure, 3 input not found or unreadable, 4 compiler/toolchain/environment failure, 5 command-line usage (docs/stability.md).

Accepted limitations

docs/limitations.md, Carried from Q1, states each, so that no claim contradicts it.

Finding register

Every finding, by area. Status: fixed (with its commit’s test), carried (with its disposition), corrected (documentation).

IdFindingSeverityDispositionStatus
Q1-B-01summation rewrite misses a counter write after = 0 (miscompile)CRITICALRELEASE-BLOCKERfixed
Q1-A2-01reaper removes foreign containers by name and kill -0HIGHRELEASE-BLOCKERfixed
Q1-A1-01no stack probes; a frame steps over the guard pageHIGHRELEASE-BLOCKERfixed; residual carried (fault, not N0408)
Q1-B-02interpreter float text breaks ties away from evenHIGHRELEASE-BLOCKERfixed
Q1-B-03UInt64 product past i128 panicsHIGHRELEASE-BLOCKERfixed
Q1-C-04nazm.root drops a manifest’s profileHIGHRELEASE-BLOCKERfixed
Q1-D-01x86_64-apple-darwin stable on one program’s evidenceHIGHRELEASE-BLOCKERfixed: corpus run under Rosetta, claim states its evidence
Q1-B-C1a cache hit refuses what a cold check acceptsHIGHRELEASE-BLOCKERfixed
Q1-C-01bounded-allocation blind behind a callHIGHGATE-BLOCKERfixed
Q1-C-02Gate 2’s allocating built-ins not countedHIGHGATE-BLOCKERfixed
Q1-C-03signed-writes bypassed by a state built out of sightHIGHGATE-BLOCKERfixed
Q1-M-09cold/warm test compares codes onlyMEDIUMGATE-BLOCKERfixed
Q1-B-04spec contradicts itself (spawn arguments, captured capabilities)MEDIUMGATE-BLOCKERcorrected
Q1-D-02six tests pass vacuously off their platformMEDIUMGATE-BLOCKERfixed: ignored there; NAZM_REQUIRE_HOST_TOOLS=1 fails a missing tool
Q1-D-03Gate 2’s mutants never campaignedMEDIUMGATE-BLOCKERsee Mutation below
Q1-A2-02host refusal of heavy work only declaredMEDIUMFIX-BEFORE-V1fixed
Q1-A1-02predictable scratch directories, then executedMEDIUMFIX-BEFORE-V1fixed
Q1-M-07check-cache facts not integrity-checkedMEDIUMFIX-BEFORE-V1fixed
Q1-M-11pool select deadline wraps and spinsMEDIUMFIX-BEFORE-V1fixed
Q1-D-05help and README deny floating pointMEDIUMFIX-BEFORE-V1fixed
Q1-A1-04C-thread export faults instead of N0408MEDIUMFIX-BEFORE-V1carried
Q1-A1-05wasm32 handle width in C structsMEDIUMFIX-BEFORE-V1carried
Q1-B-05exit_with outside 0..255 differs by engineMEDIUMFIX-BEFORE-V1carried
Q1-D-04VERIFIED sub-claims resting on ignored testsMEDIUMFIX-BEFORE-V1carried (experimental surfaces)
Q1-D-06usage errors exit 2MEDIUMFIX-BEFORE-V1fixed in Q1-C1: usage is 5
Q1-D-07user errors exit 4MEDIUMFIX-BEFORE-V1fixed in Q1-C1: 1 or 3 by kind
Q1-D-08nazm doc drops the loader’s diagnosticMEDIUMFIX-BEFORE-V1fixed (message, and status by kind in Q1-C1)
Q1-D-10published schemas closed against additive fieldsMEDIUMFIX-BEFORE-V1carried
Q1-D-18stability omits Bytes and TextMEDIUMFIX-BEFORE-V1carried to Gate 2’s close
Q1-D-19Gate 2 untested in the formatter, provenance, validatorsMEDIUMFIX-BEFORE-V1carried to Gate 2’s close
Q1-M-14failed task with a blocked sibling hangs silentlyMEDIUMACCEPTED-LIMITATIONcarried, documented
Q1-A2-03symlink loop hangs resolutionLOWFIX-BEFORE-V1fixed
Q1-A2-04publish merges into a stale version directoryLOWFIX-BEFORE-V1fixed
Q1-A2-05lockfile names a package twiceLOWFIX-BEFORE-V1fixed
Q1-A2-06“no scripts” guarded indirectlyLOWFIX-BEFORE-V1fixed
Q1-A2-07contained runner’s isolation flags untestedLOWFIX-BEFORE-V1carried
Q1-A2-08subnet overlap by octetsLOWFIX-BEFORE-V1fixed
Q1-A2-09integration-testing overclaimsLOWFIX-BEFORE-V1corrected
Q1-A1-03build work directory follows a planted symlinkLOWFIX-BEFORE-V1fixed
Q1-A1-06linker argument injection through a shared nameLOWFIX-BEFORE-V1fixed
Q1-A1-07-o over a non-entry moduleLOWFIX-BEFORE-V1fixed
Q1-A1-08package source walk follows directory symlinksLOWFIX-BEFORE-V1fixed (with Q1-A2-03)
Q1-A1-09type depth exponentialLOWFIX-BEFORE-V1fixed
Q1-A1-10guard-page mprotect uncheckedLOWFIX-BEFORE-V1fixed
Q1-M-01, -02Bytes/Text absent from the memory tables; one reclamation testLOWFIX-BEFORE-V1carried to Gate 2’s close
Q1-M-08warm diagnostic names a parameter by positionLOWFIX-BEFORE-V1carried
Q1-M-10Cranelift key relied on a hand-bumped revisionLOWFIX-BEFORE-V1fixed
Q1-M-12native select deadline on the wall clockLOWFIX-BEFORE-V1carried
Q1-B-06generic symbol names grow exponentiallyLOWFIX-BEFORE-V1carried
Q1-B-07LIR oracle lacks float formattingLOWFIX-BEFORE-V1carried
Q1-D-09unreadable root reported at a prelude spanLOWFIX-BEFORE-V1carried
Q1-D-11, -13, -14no public code registry; N0101 a catch-all; reserved codesLOWFIX-BEFORE-V1carried
Q1-D-12phantom codes N0703/N0704LOWFIX-BEFORE-V1corrected (N0357, N0358)
Q1-D-15, -16no warning severity for deprecation; strict input readers undocumentedLOWFIX-BEFORE-V1carried
Q1-D-17no compat case for epoch 34 or N0359LOWFIX-BEFORE-V1carried
Q1-D-20self-hosted compiler does not carry Gate 2, unsaidLOWFIX-BEFORE-V1carried (docs/limitations.md)
Q1-D-22fmt lays out a file that does not parseLOWACCEPTED-LIMITATIONcarried, documented
Q1-D-24stale text in VERIFIED matrix rowsLOWFIX-BEFORE-V1carried
Q1-D-25unscoped “sandbox”, “safe”, “memory safety”LOWFIX-BEFORE-V1corrected
Q1-F-01fuzz hangs counted, not keptLOWFIX-BEFORE-V1carried
Q1-FI-01interrupted build leaves its scratch directoryLOWPOST-V1carried
Q1-A2-10, -11, -14root in the container; export staging name; unlocked indexLOW/INFOPOST-V1carried
Q1-A1-11, -14, -15reserved symbols; textual unsafe gate; developer toolingLOW/INFOPOST-V1carried
Q1-B-08, -09, -10literal underscores; quadratic wide records; huge diagnosticsLOWPOST-V1carried
Q1-M-15, -16, -17, -18runtime SIGINT, NAZM_TASK_STACK wrap, select order in tests, cache doc driftINFOPOST-V1 / FIX-BEFORE-V1carried
Q1-A1-12, -13, -16; Q1-A2-12, -13, -15; Q1-M-03, -05, -06, -13stated hazards and boundariesLOW/INFOACCEPTED-LIMITATIONcarried, documented
Q1-D-21, Q1-FI-02a failed build leaves the previous outputINFOACCEPTED-LIMITATIONby design: rename on success
Q1-E-01supply-chain policy failing and unenforcedMEDIUMFIX-BEFORE-V1fixed
Q1-E-02SECURITY.md status stale; private repository has no private reportingLOWRELEASE-BLOCKER for publicationcorrected; publication blocker carried
Q1-T-01the sampler test needs CPU it can be starved ofLOWPOST-V1carried
Q1-G-01governance incident: NAZM_CONTAINED=1 set on a host for one refusal probe (it refused; nothing ran)——recorded (docs/runbook.md, A second incident, recorded); refusals now tested through child processes (xtask/tests/host_refusal.rs)

Mutation

Each fix has a focused mutant (xtask/mutations/mutations.toml, q1-*, 21) and four older mutants were repointed; the reaper’s ps probe has none because the contained runner is root, for whom the kill -0 it replaced also answers “alive” (the host test holds it).

The contained campaign over the 21 q1-* entries, Gate 2’s 38 never campaigned (Q1-D-03) and the 15 Gate 2 and Q1 repointed — 74, every killer verified pristine-passes, mutant-fails, restored-passes — caught 73 at tier 1; none survived. On the way it found three faults in its own evidence, each fixed: q1-an-unsigned-product-may-pass-i128 was an equivalent mutant (a wrapped unsigned product in i128 is always negative, so still an overflow) — it now restores the panicking x * y; the far-deadline test was clock- and race-dependent, and the defect’s only symptom on Linux is a spinning worker with a right answer — the test now measures CPU; Gate 2’s view-reclamation test read freed memory that still held its byte — it now scribbles freed memory. g2-a-view-does-not-keep-its-buffer stays uncaught: a contained probe found no difference in output or memory counts with the retain removed, so either another path holds the buffer (an equivalent mutant) or the counters cannot see it. It is Gate 2’s, and Gate 2’s contained gate owes its verdict.