Found while implementing #4486 (org-scoping the MetadataProvider seed cache). Filed separately and unassigned: it is a different boundary from that card, and #4486's fix does not close it.
What
AuthProvider.signOut clears only the bearer token:
asyncsignOut(){const{ error }=awaitbetterAuth.signOut();TokenStorage.clear();
...
}Nothing clears:
- the
objectui:metadata:* entries in sessionStorage (written by MetadataProvider), or ActiveOrganizationStorage (auth-active-organization-id in localStorage).
No sign-out call site reloads the page either — AppSidebar, AppHeader, UserMenu and RemediationOverlay all just call signOut(), so the SPA keeps running and sessionStorage (which is per-tab, not per-session) survives into whatever happens next in that tab.
Why it matters
The cached app list is the per-user list: the server filters GET /api/v1/meta/:type per session (filterAppForUser, objectstack#8013), so what sits in storage is "the apps THIS user was allowed to see". If a second person signs in in the same tab — a shared or kiosk browser, a handover, a support session — MetadataProvider seeds them from that entry and they briefly read the previous user's filtered app list.
This is a cross-principal disclosure, unlike #4486, whose reader is the same signed-in user reading a workspace they are a member of. #4486 keys the seed by active organization, which is the right fix for that card and deliberately does not address this one: two users in the same org produce the same key, so the seed still hits.
The exposure is app DEFINITION metadata (name, label, icon, branding, navigation tree, required-permission names) — not record rows and not credentials. For an org-authored app the navigation tree and permission names do describe that workspace's private model, and the per-user filtering means the list itself carries "what that person had access to".
Fix shape (for whoever takes it)
Sign-out should drop the client-side caches it leaves behind — the objectui:metadata:*sessionStorage entries and ActiveOrganizationStorage — rather than relying on the tab being closed. Worth deciding at the same time whether the seed should additionally be scoped to the user/session identity, which would make the stale entry unreadable by construction instead of depending on a cleanup step being remembered on every future sign-out path. MarketplacePackagePage already has a working precedent for the purge loop (it removes every key with the objectui:metadata: prefix).
Refs #4486 (the org-keying half, in flight).
Found while implementing #4486 (org-scoping the
MetadataProviderseed cache). Filed separately and unassigned: it is a different boundary from that card, and #4486's fix does not close it.What
AuthProvider.signOutclears only the bearer token:Nothing clears:
objectui:metadata:*entries insessionStorage(written byMetadataProvider), orActiveOrganizationStorage(auth-active-organization-idinlocalStorage).No sign-out call site reloads the page either —
AppSidebar,AppHeader,UserMenuandRemediationOverlayall just callsignOut(), so the SPA keeps running andsessionStorage(which is per-tab, not per-session) survives into whatever happens next in that tab.Why it matters
The cached app list is the per-user list: the server filters
GET /api/v1/meta/:typeper session (filterAppForUser, objectstack#8013), so what sits in storage is "the apps THIS user was allowed to see". If a second person signs in in the same tab — a shared or kiosk browser, a handover, a support session —MetadataProviderseeds them from that entry and they briefly read the previous user's filtered app list.This is a cross-principal disclosure, unlike #4486, whose reader is the same signed-in user reading a workspace they are a member of. #4486 keys the seed by active organization, which is the right fix for that card and deliberately does not address this one: two users in the same org produce the same key, so the seed still hits.
The exposure is app DEFINITION metadata (name, label, icon, branding, navigation tree, required-permission names) — not record rows and not credentials. For an org-authored app the navigation tree and permission names do describe that workspace's private model, and the per-user filtering means the list itself carries "what that person had access to".
Fix shape (for whoever takes it)
Sign-out should drop the client-side caches it leaves behind — the
objectui:metadata:*sessionStorageentries andActiveOrganizationStorage— rather than relying on the tab being closed. Worth deciding at the same time whether the seed should additionally be scoped to the user/session identity, which would make the stale entry unreadable by construction instead of depending on a cleanup step being remembered on every future sign-out path.MarketplacePackagePagealready has a working precedent for the purge loop (it removes every key with theobjectui:metadata:prefix).Refs #4486 (the org-keying half, in flight).