diff --git a/Containerfile.fresh-auth b/Containerfile.fresh-auth new file mode 100644 index 0000000..839e3a0 --- /dev/null +++ b/Containerfile.fresh-auth @@ -0,0 +1,6 @@ +# Narrow repair of the admitted review image; excludes pending intake changes. +FROM forgejo.coulomb.social/coulomb/informed-decision@sha256:0557edffb0efccc2e283cb878bc3f85deacd8165b45eefde830c21f6f02710c8 +ARG SOURCE_REVISION +LABEL org.opencontainers.image.revision=$SOURCE_REVISION +LABEL org.opencontainers.image.base.digest="sha256:0557edffb0efccc2e283cb878bc3f85deacd8165b45eefde830c21f6f02710c8" +COPY informed_decision/oidc.py /opt/venv/lib/python3.12/site-packages/informed_decision/oidc.py diff --git a/docs/browser-authentication.md b/docs/browser-authentication.md index f2b7c65..41ddc6f 100644 --- a/docs/browser-authentication.md +++ b/docs/browser-authentication.md @@ -57,6 +57,15 @@ MFA `at` is preserved as authentication time. A successful login does not assert that MFA meets an action-specific freshness policy; the policy integration must use these facts for the action. No local approval verdict is inferred. +Review sign-in requests `prompt=login` and `max_age=0` from KeyCape. Its +`KEY-WP-0033` implementation translates those parameters for Authelia 4.38 and +enforces the exact requested freshness against verified upstream authentication +time. It also refuses to reuse the existing KeyCape assurance for that login. +The earlier rollback predated this owner fix. Review policy still independently +requires signed MFA within 900 seconds; neither login nor token mint time +replaces authentication time. Portal sign-in does not refresh an existing review +session: start review sign-in again after authenticating elsewhere. + Sessions last at most fifteen minutes and never outlive either token. They are bounded to 1,024 entries, protected by a process lock, rotated on sign-in and deleted on sign-out/expiry. Restart requires reauthentication. This is a diff --git a/docs/evidence/2026-10-03-provisioning-window-review-refusal.json b/docs/evidence/2026-10-03-provisioning-window-review-refusal.json index 4a3882a..26bd14f 100644 --- a/docs/evidence/2026-10-03-provisioning-window-review-refusal.json +++ b/docs/evidence/2026-10-03-provisioning-window-review-refusal.json @@ -9,7 +9,7 @@ "mfa_age_at_request_seconds": 4781, "maximum_mfa_age_seconds": 900, "identity_and_memo_pins_match": true, - "fresh_password_and_otp_on_latest_attempt": "awaiting operator confirmation", + "fresh_password_and_otp_on_latest_attempt": "Founder reports logout through users.coulomb.social followed by password and OTP login in the same browser; review request still carries the old signed time.", "dependency": "INFD-IN-0005", "next_step": "Complete KeyCape /account/logout and upstream browser sign-out, then fresh password/OTP login. If the signed authentication time remains old after real OTP, KeyCape owns the correction and completed-browser proof.", "policy_relaxed": false, diff --git a/informed_decision/oidc.py b/informed_decision/oidc.py index 6a97be5..e110af4 100644 --- a/informed_decision/oidc.py +++ b/informed_decision/oidc.py @@ -78,8 +78,10 @@ class KeyCapeLogin: return self.issuer + "/authorize?" + urlencode({ "response_type": "code", "client_id": CLIENT_ID, "redirect_uri": CALLBACK, "scope": " ".join(REQUIRED_SCOPES), "state": state, "nonce": nonce, - # Authelia 4.38 cannot complete prompt=login/max_age=0. - # Review policy still enforces the signed MFA authentication age. + # KeyCape enforces freshness itself and translates the upstream + # request for Authelia 4.38 (KEY-WP-0033). Prevent a reused issuer + # session from carrying stale MFA into a new review login. + "prompt": "login", "max_age": "0", "code_challenge": challenge, "code_challenge_method": "S256", }), browser diff --git a/tests/test_browser_auth.py b/tests/test_browser_auth.py index 494db81..345d572 100644 --- a/tests/test_browser_auth.py +++ b/tests/test_browser_auth.py @@ -101,8 +101,8 @@ def test_pkce_nonce_scope_and_imported_human_session(login): assert session.access_token not in repr(session) assert login[1].params["scope"] == "openid approval:read approval:approve" assert login[1].params["code_challenge_method"] == "S256" - assert "prompt" not in login[1].params - assert "max_age" not in login[1].params + assert login[1].params["prompt"] == "login" + assert login[1].params["max_age"] == "0" assert login[1].params["nonce"] != login[1].params["state"] diff --git a/workplans/ADHOC-2026-10-03.md b/workplans/ADHOC-2026-10-03.md index 250b2f7..10c6527 100644 --- a/workplans/ADHOC-2026-10-03.md +++ b/workplans/ADHOC-2026-10-03.md @@ -118,8 +118,9 @@ A6 refusal diagnosis: read at 18:27:49 UTC still carried exactly the same KeyCape `assurance.at=1791047288` (17:08:08 UTC), age 4,781 seconds. Exact identity, group, memo and native approval pins match v7; the 900-second MFA limit refuses this request. The latest attempt has not supplied fresh evidence; -whether the browser actually performed a fresh OTP is awaiting operator -confirmation. Do not infer a new authentication from a successful redirect. +the founder confirms account-portal logout followed by password and OTP login +in the same browser. That did not propagate fresh assurance into this review +request. Do not infer a new review authentication from portal login. KeyCape's `/account/logout` confirmation clears its session and routes through upstream Authelia sign-out; informed-decision sign-out alone is local. Complete that browser flow, then start a new review login with password and OTP. @@ -127,3 +128,5 @@ If the verified timestamp remains old after that, KeyCape must correct the authentication-time contract under existing intake `INFD-IN-0005` and prove it with a completed browser journey. Retain all policy freshness limits. Evidence: `docs/evidence/2026-10-03-provisioning-window-review-refusal.json`. +`INFD-WP-0006` implements an explicit fresh-authentication request from review +sign-in; its narrow image rollout and native browser proof remain blocked. diff --git a/workplans/INFD-WP-0006-fresh-review-authentication.md b/workplans/INFD-WP-0006-fresh-review-authentication.md new file mode 100644 index 0000000..48c1188 --- /dev/null +++ b/workplans/INFD-WP-0006-fresh-review-authentication.md @@ -0,0 +1,56 @@ +--- +id: INFD-WP-0006 +type: workplan +title: "Request fresh issuer authentication for timed decision reviews" +domain: infotech +repo: informed-decision +status: blocked +owner: codex +created: "2026-10-03" +updated: "2026-10-03" +related: [INFD-IN-0005, KEY-WP-0033, INFD-WP-ADHOC-2026-10-03] +--- + +## Request fresh authentication on review sign-in + +```task +id: INFD-WP-0006-T01 +status: done +priority: high +``` + +The founder reported signing out through users.coulomb.social and entering +password and OTP again in the same browser, but p01 read still carried the +17:08:08 UTC authentication time. Portal and review sessions are independent. +Review sign-in currently permits reused KeyCape assurance. Request +`prompt=login` and `max_age=0` via the existing state/nonce/PKCE flow, using +KeyCape's completed KEY-WP-0033 compatibility implementation. Retain the +signed authentication timestamp and every action policy freshness check. +Update the existing browser authentication regression and document the owner +contract. No new identity assertion or local authorization decision. + +Implemented and validated: all 80 browser-authentication tests pass, including +preservation of the issuer's original signed authentication time. The candidate +uses `Containerfile.fresh-auth` to replace only `oidc.py` in the exact admitted +image, excluding pending intake implementation and schema migration. Native +browser proof remains T02. + +## Deploy and prove the fresh browser journey + +```task +id: INFD-WP-0006-T02 +status: wait +priority: high +needs_human: true +blocking_reason: "Publish and prepare a pinned review image, obtain founder approval for the live rollout, then complete human password/OTP login and verify a new signed authentication time and p01 read allowance." +``` + +Source tests are insufficient for native admission. Publish the exact tested +source and prepare the existing deployment image update with previous image +rollback; preserve runtime policy and memo pins. Live replacement needs the +founder's go under the agent environment orientation attended-window rule. +The founder alone completes password/OTP and any disposition. Verify the +signed timestamp is new and exact p01 read is allowed. If genuine fresh MFA +still reports the prior time, KeyCape must correct assurance issuance under +INFD-IN-0005; if its upstream freshness check fails, KeyCape/Authelia own that +dependency. Do not increase the 900-second action limit to mask either issue.