diff --git a/.planning/phases/10-key-material-hardening/10-CONTEXT.md b/.planning/phases/10-key-material-hardening/10-CONTEXT.md index 0bdec32b..65e1048e 100644 --- a/.planning/phases/10-key-material-hardening/10-CONTEXT.md +++ b/.planning/phases/10-key-material-hardening/10-CONTEXT.md @@ -40,11 +40,26 @@ derivation paths, word counts, or the at-rest encryption envelope. from `UNAUTHENTICATED_METHODS` — so this path does not need to be built, and it is not itself a bypass. -- **D-03:** The gate **refuses if *either* signal says onboarded** — the on-disk key file - (`NodeIdentity::key_exists`, `identity.rs:117`) **or** the onboarding flag - (`AuthManager::is_onboarding_complete`, `auth.rs:182`). Fails safe when the two disagree, - which is a real state: `auth.rs:196-207` already carries auto-heal logic for exactly that - drift. Do not pick one signal and trust it alone. +- **D-03:** The gate **refuses if *either* signal says onboarded** — never one signal trusted + alone. Fails safe when signals disagree, which is a real state: `auth.rs:196-207` already + carries auto-heal logic for exactly that drift. + +- **D-03a (mechanism correction, 2026-08-01 — intent of D-03 unchanged):** D-03 originally named + `NodeIdentity::key_exists` (`identity.rs:117`) as one of the two signals. **That signal is + unusable and would have bricked onboarding on every fresh node.** `Server::new` + (`server.rs:63-72`) calls `NodeIdentity::load_or_create` on *both* branches of its + fresh-vs-existing check, and `load_or_create` (`identity.rs:48-51`) generates and writes a + random temporary node key when none exists. So `key_exists` is `true` on every node that has + booted even once, onboarded or not — a gate keyed on it refuses `seed.generate` on a node that + has never been onboarded. + + This flaw was inherited from the audit's own suggested remediation + (`ENTROPY-SEED-AUDIT-2026-07-31.md:214-221`) and repeated in the planning brief; the planner + caught it against the code. The corrected signal set is `is_setup()` / + `is_onboarding_complete()` / `seed_exists()` — all false on a fresh node and during the seed + steps, all true afterwards. `key_exists` and `fips_key_exists` are rejected with recorded + verdicts, and the correction is pinned by a test + (`allows_on_fresh_temp_dir_even_though_node_key_exists`), not a comment. - **D-04:** 10-01 covers **every method in `UNAUTHENTICATED_METHODS` that can mutate identity or credentials**, behind the same gate and one shared test suite — not just the two endpoints