# PoC status — final gate, 2026-10-23 (after review round 6) Every number below is read from `landing/poc-output/gate.json`, which `poc/scripts/gate-json.mjs` wrote while it ran each step (ARCHITECTURE §14.1, "Gate record"). Each step records its own start and end time, exit code, parsed counts, and the poc tree hash before and after it ran. The tables in §1, §2, §5 and §6 are printed by `node scripts/gate-json.mjs status-md` from that file and were not typed by hand. - **Code measured:** poc tree `33d8870df749167c59bc4ac8c0ba19df37f8750828ac2068b42dd87ab965c842`, measured at 2026-10-23T07:47:04.343Z (the second coverage run). The hash is `landing/src/lib/poc-tree.ts` `pocTree()`: the sha256 of the path and bytes of every `.rs` and `Cargo.toml` under `crates/`, `tests-e2e/` and `fuzz/fuzz_targets/`, plus `Cargo.toml` and `Cargo.lock`. The landing page, the docs and the showcase manifest compare the same hash. The approver's TypeScript, scripts and docs are **outside** the hash. - **Every step ran on that tree and passed:** `consistent: true`. No step changed the tree while it ran. - **Machine:** the build Mac (darwin/arm64, Darwin 27.0.0, 16 cores), rustc 1.94.0 (4a4ef493e 2026-03-02) (nightly only for cargo-fuzz), `CARGO_TARGET_DIR=poc/target/final-gate`, `CARGO_INCREMENTAL=0`. The Linux runs used Docker (`rust:1.94.0`, aarch64) on the same machine. - **Time window:** the first recorded step started at 2026-10-23 07:27 UTC, and the last one ended at 2026-10-23 07:47 UTC. ## 1. Gate result | Gate step | Result | | --- | --- | | `make check` (fmt-check, clippy pedantic `-D warnings`, test, cov run 1, deny, age, wasm, check-windows, fuzz-build, fuzz-smoke-selftest, gate-json-selftest, pwa, heapcheck) | green | | `test` (`cargo test --workspace --all-targets`, inside `make check`) | **690 passed, 0 failed, 0 ignored** | | `cov` run 1 (inside `make check`) | 100 % lines (20 613 lines, 0 missed); 690 passed, 0 failed | | `cov` run 2 (`cargo llvm-cov --workspace --all-targets --locked --fail-under-lines 100`, run alone) | **100.00 % lines (20 613 / 20 613)**, 100.00 % functions (3 170 / 3 170); 690 passed, 0 failed | | `clippy --workspace --all-targets --all-features -D warnings` (feature-gated code) | green | | `deny` (cargo-deny: advisories, bans, licences, sources; inside `make check`) | green | | approver vitest with coverage (inside `make check` → `pwa`) | **104 / 104 tests, 100 % statements, 100 % branches, 100 % functions, 100 % lines** (1 030 / 1 030 lines) | | Playwright, mock relay | 6 passed, 0 failed | | Playwright, real stack (`make e2e-pwa`) | 1 passed, 0 failed | | `fuzz-smoke` (`FUZZ_SECONDS=60`) | **26 / 26 targets rc=0, 0 crashes or errors**, 63–70 s each | | Linux two-uid gate (`COV_PACKAGES="-p custos-daemon" scripts/coverage-linux.sh`, rust:1.94.0) | pass: pass 1 (non-root) 221 tests; pass 2 (root, `real_perms`) **11 / 11**; merged 100 % lines (7 797) | | `it-azure` (Lowkey Vault, Docker) | 1 passed, 0 failed | | `e2e-azure-lowkey` (UC4 Azure path, Lowkey Vault) | 1 passed, 0 failed | | Service-mode walkthrough (Linux, two real uids, `scripts/service-mode-walkthrough.sh`) | green: 22 ok, 24 refused as expected, 0 failed; "every check passed; findings to fix elsewhere: 1" | | Showcase capture (`capture-showcase.mjs`, then `--verify`) | green: 28 files match the manifest | `make check` now also runs `gate-json-selftest`, which tests the parsers behind `gate.json` at 100 % lines. Before the recorded run, the same `make check` ran once unrecorded on the same tree, and it was green (690 passed, 0 failed). No red was found in this gate, so no library code changed. Changes made by this gate (tooling and text only, no Rust source): - `poc/scripts/gate-json.mjs` and `gate-parse.mjs` (with tests) write `landing/poc-output/gate.json`. The page reads that file. - The staleness checks no longer use file modification times or git commits. The landing page (`src/data/gate.ts`: the STATUS.md fallback had used the file's mtime) and the docs (`docs/scripts/sync-poc-status.mjs`, which had used "git commit + dirty") both now compare this tree hash. - `capture-showcase.mjs` records the same hash in its manifest (`source_tree_at_start/end`). - ARCHITECTURE §14.1 (gate record) and §14.6 (the two-uid run is executed, daemon only) were updated. ## 2. Coverage and tests per crate Line coverage and test counts come from the second coverage run (`cov-2`: `cargo llvm-cov --json` per file, grouped by package; tests per test binary, assigned to packages by `cargo metadata`). Every test and every line was assigned to a package. | Crate | Lines covered | Line coverage | Tests | | --- | --- | --- | --- | | `custos-daemon` | 7 820 / 7 820 | 100.00 % | 223 | | `custos-core` | 7 090 / 7 090 | 100.00 % | 219 | | `custos-relay` | 1 432 / 1 432 | 100.00 % | 88 | | `custos-cli` | 1 367 / 1 367 | 100.00 % | 62 | | `custos-wasm` | 2 424 / 2 424 | 100.00 % | 51 | | `custos-e2e` | — | — | 24 | | `custos-mcp` | 304 / 304 | 100.00 % | 15 | | `custos-rpc` | 170 / 170 | 100.00 % | 7 | | `custos-os` | 6 / 6 | 100.00 % | 1 | | **Workspace** | **20 613 / 20 613** | **100.00 %** | **690** | Outside the workspace: `heapcheck` 4 tests (`make check`; it needs `unsafe`, so line coverage is not measured). The approver PWA (TypeScript) has 104 vitest tests at 100 % lines (1 030 / 1 030), plus 6 Playwright tests against the mock relay and 1 against the real stack. On Linux the daemon measures 7 797 lines, not the Mac's number, because `cfg(target_os)` selects different code. Both runs are 100 %. ## 3. End-to-end use cases (`tests-e2e`, real relay over TCP, real daemons on Unix sockets) | # | Use case | Test(s) | Result | | --- | --- | --- | --- | | 1 | Blind use (MCP `http_request`, response scrubbed, egress refused) | `e2e_uc1_blind_use` (+1) | pass | | 2 | Remote approval (service-mode host, WebAuthn UV, use once) | `e2e_uc2_remote_approval` (+1) | pass | | 3 | Night shift (9 h fake clock, default policy, ≤ 200 uses, confirm rule, deadline, kill switch) | `e2e_uc3_night_shift_9h_default_policy` | pass | | 4 | Bridge, don't migrate | `uc4_bridges.rs` (5 tests: Azure, 1Password with stub `op`, import and doctor, `op` under a group-writable dir refused); `e2e_uc4_bridge_azure_lowkey_vault` (Docker) | pass | | 5 | Proof, not trust (audit verify, relay DB holds no names or values, hostile relay → freeze, fork found by prefix check) | `uc5_proof.rs` (4 tests) | pass | | + | Secret drop | `e2e_drop` | pass | | + | Second approver enrollment, revoked approver's grant stops | `e2e_enroll_host_and_second_approver` | pass | | + | Canary rule (no value in any agent-visible output) | `canary_rule.rs` (4 tests; also checked after every flow) | pass | | UI | Real PWA vs real relay + daemons (UC2, UC3) | `approver/tests/real/real-stack.spec.ts` | pass | The planned e2e names that are **not built** at the e2e layer are listed in ARCHITECTURE §14.6 ([ADR 0068](../decisions/0068-spec-conformance-round-2.md)). Most of them are covered by daemon-level tests instead. ## 4. Fuzzing ([ADR 0056](../decisions/0056-owner-priority-scope-overrides.md) §4) Command: `sh scripts/fuzz-smoke.sh 60 8` (`make fuzz-smoke`), recorded at 2026-10-23 07:31 UTC. Result: **26 / 26 targets exited 0 after 63–70 s each. There were 0 crashes, 0 timeouts and 0 OOMs** (libFuzzer exits non-zero on any of them, and every target exited 0). The script's own verdict: "fuzz-smoke: all 26 target(s) ran ≥ 60s with rc=0". Execution counts ranged from 3 781 (`log_rules`) to 5 916 300 (`rpc_line`). Every per-target row is in `gate.json` `steps.fuzz-smoke.parsed.rows`. A 60 s run is a smoke test, not a fuzzing campaign. ## 5. Code review rounds 1–6 Source: [`research/14-code-review-log.md`](../research/14-code-review-log.md). The table is parsed (`scripts/gate-parse.mjs` `parseReview`) from every "## Round N — section" heading. A finding is an ID like `SECURITY-C6-1` together with its stated severity, counted once per round. "Fixed" means the finding's line says fixed. The "in part" column lists findings fixed only on one crate or side in that round, according to their own wording ("fixed (daemon half)", "(core side)", "(rpc part)"). Later sections close several of these. For example, the e2e halves of TEST-QUALITY-C4-1, C4-4 and C5-1 were closed after round 5. Findings outside a "Round" heading are not counted: these are the persona finding (N15) and SERVICE-WALKTHROUGH-2. | Round | Critical | High | Medium | Low | Logged | Fixed | of which fixed in part (one crate or side) | Not fixed | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 1 | 0 | 3 | 13 | 14 | 30 | 30 | CRYPTO-IMPL-C1-6 (low) | — | | 2 | 0 | 1 | 9 | 10 | 20 | 19 | CORRECTNESS-C2-2 (medium), CRYPTO-IMPL-C2-2 (low), TEST-QUALITY-C2-2 (medium) | CORRECTNESS-C2-4 (low) | | 3 | 1 | 3 | 7 | 12 | 23 | 23 | CORRECTNESS-C3-3 (low), TEST-QUALITY-C3-1 (medium) | — | | 4 | 0 | 1 | 7 | 17 | 25 | 25 | CRYPTO-IMPL-C4-1 (low), SECURITY-C4-2 (low), TEST-QUALITY-C4-1 (medium), TEST-QUALITY-C4-4 (low) | — | | 5 | 0 | 0 | 5 | 15 | 20 | 20 | TEST-QUALITY-C5-1 (medium) | — | | 6 | 0 | 0 | 6 | 12 | 18 | 18 | TEST-QUALITY-C6-1 (medium) | — | | **Total** | **1** | **8** | **47** | **80** | **136** | **135** | **12** | CORRECTNESS-C2-4 (low) | **What "logged" means:** the log keeps only the findings that the review handed on after its own verification step. Candidates the verifier refuted were not kept, so their number is **unknown**. **Correction:** round 1 now counts 30 findings (3 high, 13 medium, 14 low). The previous STATUS, counted by hand, said 28 (11 medium). This table is the parsed count. The other rounds match the hand count. Trend: the only critical finding came in round 3 (CORRECTNESS-C3-1, daemon). The last high came in round 4 (MALICIOUS-RELAY-C4-1, approver). Rounds 5 and 6 found medium and low issues only. The number of findings per round has not gone down much. **Still open:** - **CORRECTNESS-C2-4:** the per-apply `LogState` clone is still open (N31). The bounded checkpoint tracker half is closed (ADR 0086). - **ADR 0054:** JSON numbers beyond `i64`/`u64` precision are shown as rounded `f64` on the approval screen. - **SERVICE-WALKTHROUGH-2** (low, hardening): the systemd unit has no `NoNewPrivileges=yes`. This is the "findings to fix elsewhere: 1" line in the walkthrough transcript. - **Approver:** the PWA shows use status `→ 0` (logged before the call). ## 6. Runs beyond `make check` **Linux two-uid gate** (`landing/scripts/linux-perms.sh` → `COV_PACKAGES="-p custos-daemon" poc/scripts/coverage-linux.sh`). It started at 2026-10-23T07:13:30Z, on the daemon source tree `c3f3d0fefd5f91653eb2f152426a0038b5e9ae901e53014cc2fe8d8bb791c820` (`pocTree(DAEMON_ROOTS)`, which is still the current tree). Pass 1 (non-root): 221 tests. Pass 2 (root, `real_perms`): 11 / 11. Merged: 100 % lines (7 797 / 7 797), exit 0. It covers `custos-daemon` and its path dependencies only, not the whole workspace. **Lowkey Vault** (`nagyesta/lowkey-vault:7.3.112`, Docker, one container `custos-lowkey-final` created and then removed): `it-azure` 1 passed, 0 failed; `e2e-azure-lowkey` 1 passed, 0 failed. **Service-mode walkthrough** (`scripts/service-mode-walkthrough.sh`: Linux, two real uids, no network; transcript `demo/service-mode/transcript.txt`, re-recorded): 22 checks ok, 24 agent actions refused for the stated cause, 0 failed. Result line: "every check passed; findings to fix elsewhere: 1". The one finding is SERVICE-WALKTHROUGH-2 (§5). **Showcase** (`node scripts/capture-showcase.mjs`, then `--verify`): re-captured on this tree. 28 files match the manifest. The manifest records source tree `33d8870df749167c59bc4ac8c0ba19df37f8750828ac2068b42dd87ab965c842`, and its SHA-256 is `3761f7eb5e2debb229393752ebae5663e2689f87074f1b55fced9c7cc65f0ae0` (also in `poc/demo/README.md`). **Real-LLM proof** (`make llm-proof`, `poc/demo/llm-session//report.json`). This gate did **not** re-run it, because it spends model tokens and needs the network. The 2 recorded runs are listed below. Their reports do not record a poc tree hash, so it is **unknown** whether they ran on this tree. They predate this gate (started 2026-10-23T06:40:40.021Z, 2026-10-23T06:42:08.012Z). | Run | Model | Variants passed | Global scans | Result | | --- | --- | --- | --- | --- | | 2026-10-23T06-40-40Z-71466b | claude-sonnet-5-5 | 5 / 5 | 5 / 5 | pass | | 2026-10-23T06-42-08Z-a76c70 | claude-sonnet-5-5 | 5 / 5 | 5 / 5 | pass | Each run has 5 variants (benign, terminal-number, inject-direct, exfil-attempts, inject-indirect): claude-sonnet-5-5 via Claude Code 2.1.276, with only the custos MCP tools, against the real relay and daemon and the real PWA (Playwright approves), upstream https://postman-echo.com/get. In every run the canary value was absent from the transcript, the scratch directory, the relay database (`inspect --search`) and the Claude session files. ## 7. Owner overrides ([ADR 0056](../decisions/0056-owner-priority-scope-overrides.md)): status 1. **1Password on macOS dev mode:** built and tested with a stub `op` run as a real child process. Residual risk N34: the same-uid agent can read `OP_SERVICE_ACCOUNT_TOKEN` and daemon memory. **No live run against a real 1Password service account** has been done, because no token for the "Custos PoC" vault was available. The live test (`make e2e-1password-live`, `CUSTOS_LIVE_1P=1`) compiles (the all-features clippy run, §1), but it has never run. 2. **Windows:** `custos-daemon` compiles for `x86_64-pc-windows-gnu`, with a named-pipe transport behind `cfg(windows)`. - It has **not been run or tested on Windows**. - The pipe uses the default security descriptor (Everyone gets read access). There is no owner-only DACL. - `custos-cli` and `custos-mcp` do not compile for Windows (ADR 0068), so there is no Windows agent client yet. 3. **Azure Key Vault vs Lowkey Vault:** passes (§1, §6). The coverage run uses an in-process fake HTTP server instead. 4. **Fuzzing executed:** done (§4). ## 8. Honest limits The full list is in ARCHITECTURE §1.4. The main ones: - **N1, N4, N19:** a compromised host, or any program running as a client uid during a grant window, can use grants or receive released values. In dev mode and at isolation 3, the agent can read daemon memory. **N34:** the same applies to the 1Password token in macOS dev mode. - **N2:** time-boxing is enforced by an honest host, not by cryptography. - **N3:** a relay can withhold everything. The system then fails closed. - **N5:** redaction prevents accidents. It is not a security boundary. - **N10:** the relay sees metadata such as timing, sizes and use counts. - **N11, N18:** a revoked approver keeps `AS` epoch 0. The approver-share ciphertexts are exposed to harvest-now-decrypt-later ([ADR 0034](../decisions/0034-hndl-residual-of-approver-share-ciphertexts.md)). - **N12, N20:** no mlock; host seeds are software keys. - **N15:** remote approval without the app open depends on Web Push. Real APNs/FCM delivery to a phone has not been tested. - **N17:** forks are found by the human-run prefix check. There are no witnesses or Merkle proofs yet. - **N22:** exposure covers HTTP APIs that take the credential in one header. - **N31:** the log grows by about 5 KB per proxied use. - **What this gate does not see:** - The tree hash covers Rust sources and lock files only. A change to the approver's TypeScript does not make `gate.json` stale. The approver steps (vitest, Playwright) ran in this same session, but they are tied to the tree only by time. - The two-uid gate covers `custos-daemon`, not the whole workspace. - Coverage is line coverage. Regions are not gated. - The LLM proof was not re-run on this tree (§6). - Feature-gated code is checked by the all-features clippy run. Its tests run only in their own steps (Lowkey) or not at all (`live-1p`, no token). - Refuted review candidates were not kept (§5). - **Platform evidence:** service-mode permissions are tested with real OS permissions on Linux only. macOS service mode with the 1Password bridge is refused ([ADR 0055](../decisions/0055-coverage-gate-two-uids-1password-linux-only.md)). Windows is compile-only. - **Flakiness:** this gate ran the workspace tests three times (the unrecorded `make check`, the recorded one and cov-2), with no failure. That does not prove that the timing-dependent misses seen in earlier rounds are gone. ## 9. Deferred, and why The full list is in ARCHITECTURE §13.2. The reasons are in [ADR 0028](../decisions/0028-poc-scope-cut.md), as amended by [ADR 0056](../decisions/0056-owner-priority-scope-overrides.md) and [ADR 0068](../decisions/0068-spec-conformance-round-2.md). The main items: - **Exposure:** inject and helper modes, `run_with_secrets`, and the loopback or MITM proxy. The PoC covers proxy mode only ([ADR 0016](../decisions/0016-exposure-modes-proxy-default.md)). - **Key and log management:** `AS` rotation, recovery, add-host share grant; k-of-n quorum, cooling-off and veto window; signed snapshots and batched `use` entries (N31). - **Approver and notifications:** Web Push and WebSocket (the PoC uses long-poll, [ADR 0025](../decisions/0025-poc-live-updates-long-poll.md)); QR pairing and CPace; PRF-wrapped approver keys and attestation policies. - **Host hardening:** hardware binding of host seeds (TPM, Secure Enclave); mlock, `PT_DENY_ATTACH`; agent sandbox and IMDS blocking. - **Windows:** running and testing the daemon on Windows, an owner-only pipe DACL (needs Win32 security FFI, so `unsafe` code or a new crate), and Windows agent clients. - **1Password:** the bridge in macOS service mode, which needs a real two-uid check on macOS (ADR 0055). - **Relay transparency:** witnesses, Merkle consistency proofs, Roughtime and hybrid TLS to the relay (N17, N30). - **Release engineering:** reproducible dual-signed builds, TUF and SLSA (N13). - **Formal verification:** a Tamarin model of the protocol. ## 10. How to reproduce ```sh cd poc export CARGO_TARGET_DIR=$PWD/target/final-gate CARGO_INCREMENTAL=0 R="node scripts/gate-json.mjs run" $R clippy-all-features exit -- cargo clippy --workspace --all-targets --all-features -- -D warnings $R fuzz-smoke fuzz -- sh scripts/fuzz-smoke.sh 60 8 $R playwright-mock playwright -- sh -c 'cd approver && npx playwright test' $R playwright-real playwright -- sh -c 'cd approver && npx playwright test --config playwright.real.config.ts' # Lowkey Vault (Docker): start nagyesta/lowkey-vault:7.3.112 on 8443/8080 first, remove it after CUSTOS_IT_LOWKEY=1 $R lowkey-it cargo-test -- cargo test -p custos-daemon --features lowkey-vault --test azure_lowkey CUSTOS_IT_LOWKEY=1 $R lowkey-e2e cargo-test -- cargo test -p custos-e2e --features lowkey-vault --test uc4_azure_lowkey $R service-mode walkthrough -- bash scripts/service-mode-walkthrough.sh $R showcase showcase -- sh -c 'node scripts/capture-showcase.mjs && node scripts/capture-showcase.mjs --verify' $R make-check make-check -- make check $R cov-2 llvm-cov -- cargo llvm-cov --workspace --all-targets --locked --json --summary-only --output-path target/gate-logs/cov-2.json --fail-under-lines 100 (cd ../landing && sh scripts/linux-perms.sh) # Linux two-uid gate → gate.json "linux" node scripts/gate-json.mjs finish # LLM runs, review table, consistent node scripts/gate-json.mjs status-md # the tables above ```