key-cape/workplans/ADHOC-2026-09-27.md
tegwick 67a0869c1e
All checks were successful
Authentication acceptance / acceptance (push) Successful in 59s
Authentication acceptance / provider-contract (push) Successful in 13s
Build and Publish Container Image / build-and-push (push) Successful in 32s
Add opt-in issuer-owned access-token status and scoped revocation
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a14bfc-9643-7872-aa12-98326dcf25e7
2026-10-18 10:10:38 +02:00

5 KiB

id type title domain repo status owner created updated state_hub_workstream_id
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 5217dd06-73cb-50f8-ba64-528e421b29de

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

id: ADHOC-2026-09-27-T01
status: done
priority: high
state_hub_task_id: "ce98e4af-cfaa-5620-8f21-15ade18ee9e8"

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.

Discovery/JWKS fetch was missing the split-horizon origin headers

id: ADHOC-2026-09-27-T02
status: done
priority: high
state_hub_task_id: "7fa0bfc7-0772-57c7-8d6f-641615cab82a"

Rolling out T01 (image digest sha256:248e449a...) broke every KeyCape login, including unrelated clients (user-engine-portal), with auth_failure / provider_metadata_unavailable at /api/oidc/token on the KeyCape side and Authelia logging invalid X-Forwarded-Proto header value 'http' on GET /.well-known/openid-configuration from KeyCape's pod IP.

Root cause, unrelated to T01's change: AutheliaAdapter.setPublicOriginHeaders (the NK-WP-0041-T02/path-B fix, key-cape@3b0446e) annotates only the token exchange (newTokenRequest) with X-Forwarded-Proto/X-Forwarded-Host so Authelia can derive the public issuer for a split-horizon (in-cluster HTTP, public HTTPS) deployment. idTokenVerifier.get in verify.go — used for the provider-metadata and JWKS fetches that verify every upstream ID token — never got the same treatment; it built a bare request with only an Accept header. Authelia 4.38 fails closed on the resulting unmarked in-cluster request. This gap predates today's session (present since 3b0446e); it surfaced now because this was the first login attempted since that image went live.

Fix: extracted setPublicOriginHeaders from an AutheliaAdapter method to a package-level function taking the target public base URL explicitly, and gave idTokenVerifier its own resolved publicBaseURL (same BrowserBaseURL-else-BaseURL fallback as the adapter) so get() applies the same headers. Added TestDiscoveryFetch_CarriesPublicOriginHeaders (confirmed it fails without the fix, passes with it). Full suite green.

New image key-cape@<pending> still needs a rollout to sso-mfa/k8s/keycape/deployment.yaml before platform-root can sign in again.