Recorded while implementing #5664 (PR #5744). Static reading, not reproduced in a browser — filed so triage can decide whether it is worth measuring, not as a claimed defect.
The reading
packages/app-shell/src/providers/MetadataProvider.tsx:
function principalScope(): string {
let token: string | null = null;
try { token = TokenStorage.get(); } catch { /* SSR / storage unavailable */ }
return token ? fingerprintToken(token) : ANON_PRINCIPAL; // '@anon'
}
principalScope() is the half of the seed key that makes a objectui:metadata: entry
unreadable by a different principal — the property pinned by
MetadataProvider.crossPrincipalSeed.test.tsx as "is unreadable by the next principal even
when the purge never ran" (#5198). When TokenStorage.get() answers null that
discrimination collapses: every such session computes the same @anon scope, so one
session's permission-filtered app list is readable by the next.
When the bearer can be absent
createAuthClient.getSession() writes TokenStorage from payload.session.token on every
boot, so for a returning browser the token is normally present. Two paths that reach
principalScope() with it absent:
MetadataProvider reads the seed synchronously at mount, before getSession() has
resolved. On a browser whose stale token was just purged by
apps/console/src/lib/auth-preflight.ts (its documented job) there is nothing to read
and the mount computes @anon.- A deployment or sign-in path whose session is established by cookie alone, which
auth-preflight.ts's own header calls out: "Cookie-based sign-in (better-auth sets a
session cookie) does NOT touch this localStorage key."
What is not claimed
The window is short, and it may be unreachable in practice on every path the console
actually takes — that is the part that needs measuring rather than reasoning. Enumerating
what the app writes across a real sign-in (the method #5664 was worked with) would settle
it in one pass.
Partial mitigation already landed on #5744
PR #5744 makes a change of session user drop the previous user's client state wholesale,
including a sessionStorage sweep. That closes the cross-user half of this on any boot
where the session user actually changes. What it does not close is two sessions sharing
@anon inside a single page-load, or a browser whose session-user pointer never got
written.
Recorded while implementing #5664 (PR #5744). Static reading, not reproduced in a browser — filed so triage can decide whether it is worth measuring, not as a claimed defect.
The reading
packages/app-shell/src/providers/MetadataProvider.tsx:principalScope()is the half of the seed key that makes aobjectui:metadata:entryunreadable by a different principal — the property pinned by
MetadataProvider.crossPrincipalSeed.test.tsxas "is unreadable by the next principal evenwhen the purge never ran" (#5198). When
TokenStorage.get()answersnullthatdiscrimination collapses: every such session computes the same
@anonscope, so onesession's permission-filtered app list is readable by the next.
When the bearer can be absent
createAuthClient.getSession()writesTokenStoragefrompayload.session.tokenon everyboot, so for a returning browser the token is normally present. Two paths that reach
principalScope()with it absent:MetadataProviderreads the seed synchronously at mount, beforegetSession()hasresolved. On a browser whose stale token was just purged by
apps/console/src/lib/auth-preflight.ts(its documented job) there is nothing to readand the mount computes
@anon.auth-preflight.ts's own header calls out: "Cookie-based sign-in (better-auth sets asession cookie) does NOT touch this localStorage key."
What is not claimed
The window is short, and it may be unreachable in practice on every path the console
actually takes — that is the part that needs measuring rather than reasoning. Enumerating
what the app writes across a real sign-in (the method #5664 was worked with) would settle
it in one pass.
Partial mitigation already landed on #5744
PR #5744 makes a change of session user drop the previous user's client state wholesale,
including a
sessionStoragesweep. That closes the cross-user half of this on any bootwhere the session user actually changes. What it does not close is two sessions sharing
@anoninside a single page-load, or a browser whose session-user pointer never gotwritten.