docs(nk-wp-0033): record the 2026-08-27 attended session findings
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

platform-root login is restored and the resolver holds the current bind
credential, so T04's outstanding reconciliation is done. No green receipt
yet — the run of record is a FAIL at resolver-lookup, and T05 stays open.

Records four things the session established:

- the predecessor is dead, observed twice by hand, but not by receipt;
- the value is unrecoverable, because no KeePassXC database has ever
  existed despite platform-root-custody.md naming a safe entry, and the
  only copy lived in a Firefox entry overwritten during the session.
  T04 replaced the credential with no step to update operator custody —
  the root cause of the whole session;
- the reconciliation script had never completed a run (4a38511);
- verify-t06.sh reported success at bootstrap against a resolver that
  was misconfigured and a reconciliation path that could not execute.
  That blind spot is open and is the finding worth acting on.

Also corrects the record: the stale bind credential was real but did not
cause the lookup failure. The HTTP 400 was our own request builder.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 3377672@bnt-lap001
Assistant-Session: 15463ccf-238f-4e13-b163-93aa25c6d166
This commit is contained in:
tegwick 2026-08-27 22:20:48 +02:00
parent 4a38511d11
commit 0f5f56275c
2 changed files with 75 additions and 1 deletions

View file

@ -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 resolvers
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.

View file

@ -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.