diff --git a/.custodian-brief.md b/.custodian-brief.md index f6a724b..428115a 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,11 +2,17 @@ # Custodian Brief — user-engine **Domain:** communication -**Last synced:** 2026-10-11 21:14 UTC +**Last synced:** 2026-10-14 07:30 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams +### Runtime image OS-package hardening for RISK-F-0016 +Progress: 0/1 done | workplan_id: `5ce5f3eb-e1dd-59bc-9cce-b0172431b9f0` + +**Open tasks:** +- · Remove or replace unfixed-HIGH OS packages in the runtime image `59584b44` + ### User account journeys and recovery Progress: 1/3 done | workplan_id: `145df9d5-a7e9-5d20-8280-9d3ea069838b` @@ -36,7 +42,8 @@ Progress: 2/3 done | workplan_id: `0aea0a52-13f9-515b-bda8-665f8a4f2d5e` Progress: 1/2 done | workplan_id: `35aa6adf-255a-5705-9294-a50d98e45f00` **Open tasks:** -- · Deliver the setup link to the recipient `fae614ba` +- ! Deliver the setup link to the recipient `fae614ba` + *(wait: Existing mail service lacks an admitted provider password-setup template and recipient-bound opaque issuer handoff. EMAIL-IN-0001 and NET-IN-0005 request exact owner implementation records; no new founder choice or credential request.)* ### Read-only account authority for Hub integration Progress: 2/3 done | workplan_id: `54d99faf-4c53-5927-a82c-feca41de2cf3` @@ -45,6 +52,10 @@ Progress: 2/3 done | workplan_id: `54d99faf-4c53-5927-a82c-feca41de2cf3` - ! Authenticated workload transport and private acceptance `c5d67725` *(wait: T02 delivered empty no-grant custody integration; real independently authenticated Hub caller credential admission and withdrawal/outage/private integration acceptance remain with state-hub (original f3bcc516; renewed request 350c3e12-284c-43a7-9ab1-116df8bcc007). Hub repository registration is not caller admission.)* +## Inbox Hygiene + +**Stale unread:** 4 message(s) older than 3 day(s) — triage at session start. + --- ## MCP Orientation (when available) diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index c330590..6addaac 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -48,6 +48,7 @@ | workplan | USER-WP-0035 | blocked | — | workplans/USER-WP-0035-onboarding-handoff-findings.md | | workplan | USER-WP-0036 | finished | — | workplans/USER-WP-0036-account-situational-awareness.md | | workplan | USER-WP-0037 | blocked | — | workplans/USER-WP-0037-hub-authority-lookup.md | +| workplan | USER-WP-0038 | ready | — | workplans/USER-WP-0038-runtime-image-os-package-hardening.md | | task | USER-WP-ADHOC-2026-09-06-T01 | done | — | workplans/ADHOC-2026-09-06.md | | task | USER-WP-ADHOC-2026-10-09-T01 | done | — | workplans/ADHOC-2026-10-09.md | | task | USER-WP-ADHOC-2026-10-09-T02 | done | — | workplans/ADHOC-2026-10-09.md | @@ -238,6 +239,7 @@ | task | USER-WP-0036-T03 | done | — | workplans/USER-WP-0036-account-situational-awareness.md | | task | USER-WP-0037-T01 | done | — | workplans/USER-WP-0037-hub-authority-lookup.md | | task | USER-WP-0037-T02 | done | — | workplans/USER-WP-0037-hub-authority-lookup.md | +| task | USER-WP-0038-T01 | todo | — | workplans/USER-WP-0038-runtime-image-os-package-hardening.md | | task | USER-WP-0037-T03 | wait | — | workplans/USER-WP-0037-hub-authority-lookup.md | | intake | USER-IN-0001 | answered | — | intakes/intakes.md | | intake | USER-IN-0002 | open | — | intakes/intakes.md | diff --git a/src/user_engine/web.py b/src/user_engine/web.py index d12ef55..1366ced 100644 --- a/src/user_engine/web.py +++ b/src/user_engine/web.py @@ -142,7 +142,10 @@ class PortalApplication: def __call__(self, environ: Mapping[str, Any], start_response: StartResponse) -> Iterable[bytes]: correlation_id = environ.get("HTTP_X_REQUEST_ID") or f"corr_{secrets.token_hex(12)}" navigation_token = _ACCOUNT_NAVIGATION.set("") - cache_token = _REQUEST_CACHE.set({}) + # The cookie header stays in the per-request cache only so the + # Authelia uid can be looked up once while rendering; it is never + # rendered or logged. + cache_token = _REQUEST_CACHE.set({"cookie_header": str(environ.get("HTTP_COOKIE", ""))}) browser_token = _BROWSER_REQUEST.set( "text/html" in str(environ.get("HTTP_ACCEPT", "")) and not str(environ.get("PATH_INFO", "/")).startswith("/api/") @@ -2321,17 +2324,47 @@ Use the login name they provide; it may differ from your display name.

You are not signed in.

" f"{level}" ) - name = escape(actor.preferred_username or actor.subject) verification = "Verified by your identity provider" if actor.assurance else "Verification pending" return ( '

Identity

' - f"

Signed in as {name}.

" - "

This session is for the account site. An application can keep its own session.

" + + self._session_uids(actor) + + "

This session is for the account site. An application can keep its own session.

" f"{level}" f"

{escape(verification)}

" '

Password and one-time code help

' ) + def _session_uids(self, actor: Any) -> str: + """Name the account-site uid and the Authelia uid on separate lines. + + NK-WP-0051-T03 (contract: net-kingdom docs/sign-in-diagnosis.md). + Only the two uids are shown; the Authelia uid comes from the existing + server-side /api/state lookup and is never guessed. + """ + account_uid = actor.preferred_username or actor.subject + authelia_uid = self._netkingdom_name() + lines = ( + '
' + "
Account site session uid
" + f'
{escape(account_uid)}
' + "
Authelia session uid (shared NetKingdom sign-in)
" + + ( + f'
{escape(authelia_uid)}
' + if authelia_uid + else '
not signed in / unknown
' + ) + + "
" + ) + if authelia_uid and authelia_uid != account_uid: + lines += ( + '" + + self._identity_switch_help() + ) + return lines + @staticmethod def _is_exception_account(actor: Any, memberships: tuple[Any, ...]) -> bool: if {"tenant-admin", "platform-operator", "platform-root"}.intersection(actor.roles): @@ -2526,9 +2559,13 @@ Use the login name they provide; it may differ from your display name.

sample.user", body) + self.assertNotIn(b"Signed in as platform-root", body) self.assertNotIn(b'href="/login"', body) - self.assertEqual([], self.app.identity_lookup.headers) + self.assertNotIn(b"super-secret-session", body) + self.assertEqual(1, len(self.app.identity_lookup.headers)) + + # NK-WP-0051-T03: two labelled uid lines and a visible mismatch marker. + def _uid_lines(self, path, lookup_result, cookie="ue_session=member; authelia_session=super-secret-session"): + self.app.identity_lookup = RecordingLookup(lookup_result) + response, body = invoke(self.app, path, cookie=cookie) + self.assertEqual("200 OK", response["status"]) + return body + + def test_signed_in_page_names_both_session_uids(self): + for path in ["/", "/onboarding"]: + with self.subTest(path=path): + body = self._uid_lines(path, "sample.user") + self.assertIn(b'
sample.user
', body) + self.assertIn(b'
sample.user
', body) + self.assertIn(b"Account site session uid", body) + self.assertIn(b"Authelia session uid", body) + self.assertNotIn(b"session-uid-mismatch", body) + self.assertEqual(1, len(self.app.identity_lookup.headers)) + + def test_differing_authelia_uid_is_marked(self): + for path in ["/", "/onboarding"]: + with self.subTest(path=path): + body = self._uid_lines(path, "bernd.worsch-3") + self.assertIn(b'
sample.user
', body) + self.assertIn(b'
bernd.worsch-3
', body) + self.assertIn(b'role="alert" class="session-uid-mismatch"', body) + self.assertIn(b"These sessions differ.", body) + self.assertIn(b"https://kc.example/account/logout", body) + self.assertNotIn(b"super-secret-session", body) + + def test_unknown_authelia_uid_is_not_guessed(self): + cases = [ + (None, "ue_session=member; authelia_session=opaque"), + (TimeoutError("slow"), "ue_session=member; authelia_session=opaque"), + ("", "ue_session=member; authelia_session=opaque"), + ("sample.user", "ue_session=member"), + ] + for result, cookie in cases: + with self.subTest(result=result, cookie=cookie): + body = self._uid_lines("/", result if cookie.count("authelia") else None, cookie) + self.assertIn(b'
not signed in / unknown
', body) + self.assertNotIn(b"session-uid-mismatch", body) + self.assertNotIn(b"", "platform root"]: diff --git a/workplans/USER-WP-0038-runtime-image-os-package-hardening.md b/workplans/USER-WP-0038-runtime-image-os-package-hardening.md index e8485ff..516eabc 100644 --- a/workplans/USER-WP-0038-runtime-image-os-package-hardening.md +++ b/workplans/USER-WP-0038-runtime-image-os-package-hardening.md @@ -13,6 +13,7 @@ related: - RISK-F-0016 - RAPP-USER-ENGINE-WP-0004 - USER-WP-ADHOC-2026-10-11 +state_hub_workstream_id: "5ce5f3eb-e1dd-59bc-9cce-b0172431b9f0" --- # Runtime image OS-package hardening @@ -35,6 +36,7 @@ on Python 3.12 slim-trixie base `a6e34c59` id: USER-WP-0038-T01 status: todo priority: high +state_hub_task_id: "59584b44-a7b7-5637-98fb-a746b5bc6c00" ``` Target the 2026-10-14 HIGH set: util-linux family (bsdutils, mount, libblkid, diff --git a/workplans/USER-WP-0039-sign-in-diagnosis-session-uids.md b/workplans/USER-WP-0039-sign-in-diagnosis-session-uids.md new file mode 100644 index 0000000..8940966 --- /dev/null +++ b/workplans/USER-WP-0039-sign-in-diagnosis-session-uids.md @@ -0,0 +1,61 @@ +--- +id: USER-WP-0039 +type: workplan +title: "Sign-in diagnosis: account-site and Authelia uid on the account page" +domain: communication +repo: user-engine +status: active +owner: claude +topic_slug: communication +created: "2026-10-14" +updated: "2026-10-14" +related: + - NK-WP-0051-T03 + - USER-WP-0036 +--- + +# Sign-in diagnosis: two session uids + +Owner side of NetKingdom NK-WP-0051-T03. Contract: +`net-kingdom/docs/sign-in-diagnosis.md` (only source of field names and the +never-show list). + +Problem: `users.coulomb.social` stayed signed in as `bernd.worsch-3` while +`bao login` reused a different Authelia session, and nothing showed the +mismatch. + +Approach: reuse the existing server-side Authelia `/api/state` lookup +(`identity_state.AutheliaIdentityState`, already configured in +rapp-user-engine via `USER_ENGINE_IDENTITY_STATE_URL` / +`USER_ENGINE_IDENTITY_STATE_HOST`). The account site already receives the +`authelia_session` cookie because Authelia's cookie domain is +`coulomb.social`; no ingress, forward-auth, or cookie-domain change is +needed. The account-site session is unchanged. + +## Show both session uids and mark a mismatch + +```task +id: USER-WP-0039-T01 +status: done +priority: high +``` + +The Identity section on `/` and `/onboarding` shows two labelled lines: +account-site session `uid` and Authelia session `uid`. When they differ a +`role="alert"` marker names both and links the shared-sign-in logout. When +the Authelia uid is unavailable it reads "not signed in / unknown". Only +the two uids are rendered; no email, token, cookie, or OTP. Tests in +`tests/test_account_identity_disclosure.py`. + +## Deploy and record evidence for NK-WP-0051 + +```task +id: USER-WP-0039-T02 +status: todo +priority: high +``` + +Build and roll the image through rapp-user-engine (no manifest change +needed), check the two lines on `users.coulomb.social` with a mismatched +session, and report the image digest to net-kingdom for the contract's +evidence table (`user-engine change`).