Oliver Kieselbach
5f3ae2d06b
fix(backend): SessionTenantLookup is claimed first-writer-wins at registration — the sessionId→tenantId row that every Global Admin/Reader cross-tenant session resolve trusts was an unconditional last-writer-wins upsert AFTER the session write keyed by the client-supplied sessionId, so any tenant's device (self-service onboarding) that knew a victim session GUID (portal deep links, Teams/Slack notifications, progress-portal URLs) could re-point the mapping at its own tenant and have every no-tenantId GA read and GA write (globaladmin annotation lane, restore fallback) silently served from a forged session; StoreSessionAsync now claims the row create-only BEFORE any Sessions/SessionsIndex write (409 → read owner: same tenant = re-registration, foreign tenant = SessionTenantConflictException → 409 session_owner_mismatch without disclosing the owner + SessionTenantConflict ops event, nothing written, not fail-soft), the legacy self-heal write is Add-only, ResolveSessionScopeAsync(requireGlobalAdmin) corroborates the resolved tenant against its Sessions row (silent, no operator confirmation), and scripts/Migration/Backfill-SessionTenantLookup.ps1 closed the pre-table window (13509 sessions, 8795 rows added, verify dry-run added=0); no tombstone on deletion by design
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 20:34:50 +02:00
..
2026-08-30 12:13:03 +02:00
2026-08-21 13:40:47 +02:00
2026-08-13 13:39:03 +02:00
2026-07-20 11:29:53 +02:00
2026-08-29 20:04:04 +02:00
2026-08-07 10:52:35 +00:00
2026-08-23 12:19:41 +02:00
2026-08-30 11:38:27 +02:00
2026-08-30 16:00:28 +02:00
fix(backend): SessionTenantLookup is claimed first-writer-wins at registration — the sessionId→tenantId row that every Global Admin/Reader cross-tenant session resolve trusts was an unconditional last-writer-wins upsert AFTER the session write keyed by the client-supplied sessionId, so any tenant's device (self-service onboarding) that knew a victim session GUID (portal deep links, Teams/Slack notifications, progress-portal URLs) could re-point the mapping at its own tenant and have every no-tenantId GA read and GA write (globaladmin annotation lane, restore fallback) silently served from a forged session; StoreSessionAsync now claims the row create-only BEFORE any Sessions/SessionsIndex write (409 → read owner: same tenant = re-registration, foreign tenant = SessionTenantConflictException → 409 session_owner_mismatch without disclosing the owner + SessionTenantConflict ops event, nothing written, not fail-soft), the legacy self-heal write is Add-only, ResolveSessionScopeAsync(requireGlobalAdmin) corroborates the resolved tenant against its Sessions row (silent, no operator confirmation), and scripts/Migration/Backfill-SessionTenantLookup.ps1 closed the pre-table window (13509 sessions, 8795 rows added, verify dry-run added=0); no tombstone on deletion by design
2026-08-30 20:34:50 +02:00
2026-07-29 10:30:47 +02:00
2026-08-22 21:00:53 +02:00
2026-07-27 09:10:52 +02:00
2026-08-21 00:44:29 +02:00
2026-07-24 18:15:23 +02:00
2026-08-13 09:27:31 +02:00
2026-08-29 21:32:24 +02:00
2026-08-27 18:10:19 +02:00
2026-08-23 16:04:31 +02:00
2026-08-23 19:15:41 +02:00
2026-08-29 23:57:35 +02:00
2026-08-23 12:19:41 +02:00
2026-08-27 18:10:19 +02:00
2026-08-29 11:57:55 +02:00