key-cape/workplans/ADHOC-2026-09-27.md
tegwick 911e9de969
All checks were successful
Authentication acceptance / acceptance (push) Successful in 1m38s
Authentication acceptance / provider-contract (push) Successful in 17s
Build and Publish Container Image / build-and-push (push) Successful in 53s
Don't backdate a fresh MFA to a stale kc_login cookie (INFD-IN-0005)
completeAuthorization reused an existing kc_login session's IssuedAt as
authTime whenever one existed for the same user, even at the two call
sites that had just validated an MFA token in this exact request. A
platform-root sign-in that stepped up with fresh MFA while an older
browser session cookie was still around got that older timestamp
stamped onto the new authentication, understating freshness for every
downstream binding-grade check (flex-auth's 900s review window denied
informed-decision's infd-20260927-b01 approval as a result).

Add a freshMFA parameter so only the carried-forward-assurance call
site (no MFA challenge in this request) reuses the prior session's
IssuedAt; the two call sites following a successful
MFA.ValidateMFAToken always stamp authTime to now.

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

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 169987@bnt-lap001
Assistant-Session: 322ef1ef-9048-4021-8570-b6d6f6347999
2026-09-27 19:03:06 +02:00

3 KiB

id type title domain repo status owner created updated
ADHOC-2026-09-27 workplan Fresh MFA no longer backdated to a stale kc_login cookie (INFD-IN-0005) infotech key-cape finished claude 2026-09-27 2026-09-27

Stop completeAuthorization from reusing a stale prior session's IssuedAt after real MFA

id: ADHOC-2026-09-27-T01
status: done
priority: high

informed-decision intake INFD-IN-0005 (open, origin_ref: SECRETS-WP-0010): platform-root's approval of infd-20260927-b01 was refused by flex-auth's compact_sitting policy with policy_denied, despite platform-root having just completed a fresh key-cape MFA sign-in. flex-auth's 900-second review-freshness check reads assurance.at, which key-cape derives from the PKCE session's AuthTime.

Root cause in src/internal/server/oidc/authorize.go, AuthorizeHandler.completeAuthorization: whenever a kc_login cookie already existed for the same username, its IssuedAt was unconditionally reused as authTime — even on the two call sites that had just called MFA.ValidateMFAToken successfully in this exact request (the callback's mfaToken != "" branch and serveMFASubmission). A user who had an older browser session and then stepped up with a fresh MFA challenge got the old session's timestamp stamped onto the new, just-proven authentication — understating freshness for every downstream binding-grade check, exactly as INFD-IN-0005 describes ("completeAuthorization currently copies a prior timestamp... only after actual reauthentication").

The reuse itself is correct when MFA was not just performed in this request (decision.MFAVerified carried forward from an existing AAL2 session, via domain.DecideAssurance) — reporting mint time there would overstate freshness (TestAssuranceReportsAuthenticationTimeNotMintTime). The two cases were conflated under a single mfaVerified bool.

Fix: added a freshMFA bool parameter to completeAuthorization, true only at the two call sites that just validated an MFA token in this request, false at the carried-forward-assurance call site. authTime now falls back to the prior session's IssuedAt only when !freshMFA.

Added TestAuthorizeCallback_MFASubmission_FreshEvenWithStalePriorLoginCookie, which seeds a 4-hour-old kc_login cookie for the same user, then submits a valid MFA token and asserts the resulting AuthTime is within a minute of now. Confirmed it fails against the pre-fix code (AuthTime ~4h old) and passes after. Full suite (go test ./...) green, no other test needed adjustment — the carried-forward-assurance case (TestPolicy_..., TestLogout_..., assurance_time_test.go) is unaffected since none of it exercises a fresh MFA submission with a pre-existing cookie for the same user.

Does not touch flex-auth (its 900s window is correct; it was consuming a bad timestamp) or informed-decision (correctly refuses to interpret the PDP's denial). INFD-IN-0005 can close once informed-decision confirms platform-root can approve after a fresh sign-in against this build.