Compare commits
2 commits
c123cd11a3
...
80f5870ebb
| Author | SHA1 | Date | |
|---|---|---|---|
| 80f5870ebb | |||
| 71feaf391b |
6 changed files with 176 additions and 12 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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.</p></sect
|
|||
"<p>You are not signed in.</p>"
|
||||
f"{level}</section>"
|
||||
)
|
||||
name = escape(actor.preferred_username or actor.subject)
|
||||
verification = "Verified by your identity provider" if actor.assurance else "Verification pending"
|
||||
return (
|
||||
'<section aria-labelledby="login-state"><h2 id="login-state">Identity</h2>'
|
||||
f"<p>Signed in as <strong>{name}</strong>.</p>"
|
||||
"<p>This session is for the account site. An application can keep its own session.</p>"
|
||||
+ self._session_uids(actor)
|
||||
+ "<p>This session is for the account site. An application can keep its own session.</p>"
|
||||
f"{level}"
|
||||
f"<p>{escape(verification)}</p>"
|
||||
'<p><a href="/security">Password and one-time code help</a></p></section>'
|
||||
)
|
||||
|
||||
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 = (
|
||||
'<dl class="session-uids">'
|
||||
"<dt>Account site session <code>uid</code></dt>"
|
||||
f'<dd data-session="account-site"><strong>{escape(account_uid)}</strong></dd>'
|
||||
"<dt>Authelia session <code>uid</code> (shared NetKingdom sign-in)</dt>"
|
||||
+ (
|
||||
f'<dd data-session="authelia"><strong>{escape(authelia_uid)}</strong></dd>'
|
||||
if authelia_uid
|
||||
else '<dd data-session="authelia">not signed in / unknown</dd>'
|
||||
)
|
||||
+ "</dl>"
|
||||
)
|
||||
if authelia_uid and authelia_uid != account_uid:
|
||||
lines += (
|
||||
'<p role="alert" class="session-uid-mismatch"><strong>These sessions differ.</strong> '
|
||||
f"Applications that use the shared NetKingdom sign-in, such as OpenBao, "
|
||||
f"will continue as <strong>{escape(authelia_uid)}</strong>, "
|
||||
f"not <strong>{escape(account_uid)}</strong>.</p>"
|
||||
+ 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.</p></sect
|
|||
if "netkingdom_name" in cache:
|
||||
return cache["netkingdom_name"]
|
||||
name: str | None = None
|
||||
if environ is not None and self.identity_lookup is not None:
|
||||
cookie_header = (
|
||||
str(environ.get("HTTP_COOKIE", "")) if environ is not None
|
||||
else cache.get("cookie_header")
|
||||
)
|
||||
if cookie_header is not None and self.identity_lookup is not None:
|
||||
try:
|
||||
found = self.identity_lookup.username(str(environ.get("HTTP_COOKIE", "")))
|
||||
found = self.identity_lookup.username(cookie_header)
|
||||
except Exception:
|
||||
found = None
|
||||
if is_identity_name(found):
|
||||
|
|
|
|||
|
|
@ -55,11 +55,62 @@ class AccountIdentityDisclosureTests(unittest.TestCase):
|
|||
_, body = invoke(
|
||||
self.app, "/", cookie="ue_session=member; authelia_session=super-secret-session"
|
||||
)
|
||||
self.assertIn(b"Signed in as", body)
|
||||
self.assertIn(b"sample.user", body)
|
||||
self.assertNotIn(b"platform-root", body)
|
||||
# The account-site session stays; the other Authelia uid is only shown.
|
||||
self.assertIn(b"Signed in as <strong>sample.user</strong>", body)
|
||||
self.assertNotIn(b"Signed in as <strong>platform-root</strong>", 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'<dd data-session="account-site"><strong>sample.user</strong></dd>', body)
|
||||
self.assertIn(b'<dd data-session="authelia"><strong>sample.user</strong></dd>', body)
|
||||
self.assertIn(b"Account site session <code>uid</code>", body)
|
||||
self.assertIn(b"Authelia session <code>uid</code>", 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'<dd data-session="account-site"><strong>sample.user</strong></dd>', body)
|
||||
self.assertIn(b'<dd data-session="authelia"><strong>bernd.worsch-3</strong></dd>', 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"),
|
||||
("<script>x</script>", "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'<dd data-session="authelia">not signed in / unknown</dd>', body)
|
||||
self.assertNotIn(b"session-uid-mismatch", body)
|
||||
self.assertNotIn(b"<script>x", body)
|
||||
|
||||
def test_without_lookup_authelia_uid_is_unknown(self):
|
||||
self.app.identity_lookup = None
|
||||
_, body = invoke(self.app, "/", cookie="ue_session=member; authelia_session=opaque")
|
||||
self.assertIn(b'<dd data-session="authelia">not signed in / unknown</dd>', body)
|
||||
self.assertNotIn(b"session-uid-mismatch", body)
|
||||
self.assertNotIn(b"opaque", body)
|
||||
|
||||
def test_lookup_failure_or_unsafe_name_stays_signed_out(self):
|
||||
for result in [TimeoutError("slow"), "<script>alert(1)</script>", "platform root"]:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
61
workplans/USER-WP-0039-sign-in-diagnosis-session-uids.md
Normal file
61
workplans/USER-WP-0039-sign-in-diagnosis-session-uids.md
Normal file
|
|
@ -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`).
|
||||
Loading…
Add table
Add a link
Reference in a new issue