diff --git a/docs/keycape-exposure-resolver-reconciliation.md b/docs/keycape-exposure-resolver-reconciliation.md index 94c7142..136f7ec 100644 --- a/docs/keycape-exposure-resolver-reconciliation.md +++ b/docs/keycape-exposure-resolver-reconciliation.md @@ -2,7 +2,7 @@ Incident: `KEYCAPE-EXPOSURE-20260823-01` Workplan: `NK-WP-0033` -NetKingdom procedure: `sso-mfa/k8s/privacyidea/reconcile-lldap-resolver-live.sh` (revision pinned by the approved checkout) +NetKingdom procedure: `sso-mfa/k8s/privacyidea/reconcile-lldap-resolver-live.sh` (repaired 2026-08-27 in `4a38511`; the previously pinned revision could not complete a run — see NK-WP-0033 T05) Platform recovery contract: railiance-platform `453fed3` Owner cutover receipt: State Hub message `45b236c8-052f-43d3-a472-44f8e9694da2` @@ -99,3 +99,27 @@ T03 may move to done only after the helper run and cleanup receipt are recorded by the attended operator. T05 may move to done only after the resolver’s replacement success, predecessor denial, owner cutover receipt, and all residual limitations are recorded as sanitized evidence. + +## Revision note — 2026-08-27 + +The pinned revision of `reconcile-lldap-resolver-live.sh` had never completed a +run. Four defects were found by running it and are fixed in `4a38511`; see +`NK-WP-0033` T05 for the full findings. Two change how this procedure is +invoked: + +- `--predecessor-unavailable` — use when the exposed predecessor cannot be + produced. The denial bind is not attempted and the receipt records + `predecessor denial=NOT-PROVEN`. Do **not** type a placeholder at the + predecessor prompt instead: a wrong value also fails the bind, and the run + records it as a *passing* denial proof — a receipt asserting a test that never + ran. +- `--note TEXT` — operator context carried verbatim in the receipt line, so a + claim and its caveat travel together. One line, 200 characters, no + credentials. + +`TIMEOUT`, `CACHE_TIMEOUT` and `SIZELIMIT` are now sent with the resolver body +(default 5 / 120 / 500, overridable via the matching `LDAP_*` environment +variables). Before this fix, `--apply` dropped them, because a resolver write +replaces the whole object. A resolver with them unset still resolves users, but +the WebUI refuses to save or test it — so a hand repair was silently reverted by +the next run. diff --git a/workplans/NK-WP-0033-keycape-secret-exposure-rotation.md b/workplans/NK-WP-0033-keycape-secret-exposure-rotation.md index df61c5e..11bfa72 100644 --- a/workplans/NK-WP-0033-keycape-secret-exposure-rotation.md +++ b/workplans/NK-WP-0033-keycape-secret-exposure-rotation.md @@ -168,3 +168,53 @@ rollout status, positive authentication/MFA outcomes, predecessor rejection or expiry, cleanup receipts, and residual limitations. Close the incident only after all four credential classes have an authoritative disposition and no temporary plaintext or stale bundle remains. + +### Attended session 2026-08-27 — findings + +The resolver reconciliation was attempted and the operator's `platform-root` +login is restored. `bao login -method=oidc` succeeds, and +`GET /user/?realm=coulomb&username=platform-root` returns the record. **No +green receipt has been produced yet**; the run below is the state of record. + +**LLDAP bind credential — reconciled.** The `--apply` run wrote the current +`LLDAP_LDAP_USER_PASS` into resolver `lldap-coulomb`, and the replacement +authenticated against LLDAP in the same run. The resolver had been holding the +pre-cutover value since 2026-08-23, which is what T04 left outstanding. + +**Predecessor disposition — NOT PROVEN by receipt; observed twice by hand.** +The exposed value is dead: the operator was locked out of the LLDAP WebUI on +2026-08-27 after logging out of a persisted session, and the `--check` run the +same day passed `predecessor-denial` with the real value before it was lost. +Neither observation is carried by an emitted receipt. The value is +unrecoverable — see the custody gap below. + +**Custody gap — the actual root cause of this session.** T04 replaced the +credential without a step that updates operator custody, and there was no +custody store to update it into. `platform-root-custody.md` names a password +safe entry `net-kingdom/LLDAP/admin`; **no KeePassXC database exists** and never +did. The value lived only in a Firefox password-manager entry dated 2026-06-28, +which was overwritten with the replacement on 2026-08-27, destroying the +predecessor. Any rotation runbook that does not name where the outgoing value is +retained will lose it the same way. + +**The reconciliation script had never completed a run.** Four defects, fixed in +`4a38511`: `Content-Type` on bodyless GETs (HTTP 400 on every GET), `/user/` +parsed as a dict when it returns a list (traceback past the except clause), +resolver writes dropping `TIMEOUT`/`CACHE_TIMEOUT`/`SIZELIMIT` (silently +un-repairing a hand-fixed resolver), and no way to declare an unavailable +predecessor (forcing a placeholder that records a *passing* denial proof). + +**Verification blind spot — open.** `verify-t06.sh` reported success at +bootstrap against a resolver created without the three tuning parameters, by a +reconciliation path that could not execute. A verification that passes while its +subject cannot run is not verification. This is the finding worth acting on +beyond the four fixes, and it is not yet addressed. + +**Diagnosis correction.** The "stale bind credential" reading in T04 was real but +was not what blocked the resolver lookup; the HTTP 400 was our own request +builder throughout. The two faults were independent and looked like one, which +is why each partial fix appeared to change nothing. + +Remaining before T05 can close: a green receipt from the repaired script, and a +ruling on whether a predecessor disposition observed but not receipted is an +acceptable close for this incident.