You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
clear_session leaves both current-user caches intact: same-JWT re-login can replay pre-logout state #5758
clear_session — the canonical sign-out path — leaves both process-global current-user caches intact.
There are two of them, declared next to each other in src/openhuman/desktop/app_state/ops.rs:
CURRENT_USER_CACHE — the positive cache, holding the last successful /auth/me snapshot.
CURRENT_USER_FAILURE — its negative counterpart, holding the last availability failure so the next poll can be suppressed rather than sat on for the full fetch timeout.
Both are invalidated at exactly one place, and it is not the logout path:
clear_session (src/openhuman/security/credentials/ops.rs:788) removes the auth profile, tears down the socket, clears active_user.toml, stops login-gated services, rebinds process globals and clears the Sentry scope — but touches neither cache. A tree-wide grep finds no other writer of either global, which is what makes this a single-site fix rather than an audit.
Consequence
Both caches are keyed on (api_base, token). Signing out and back in with the same JWT inside the relevant window can therefore replay pre-logout state:
Positive cache: the pre-logout user snapshot is served for the remainder of CURRENT_USER_REFRESH_TTL.
Negative cache: the recorded pre-logout error is replayed for up to the backoff ceiling — 60s.
Bounds — this is narrower than it first reads
Worth stating explicitly so this is not scoped as a broad session-integrity break:
A different token on re-login misses both caches entirely; the key check is on (api_base, token), so this needs the same JWT stored again.
The negative-cache replay is gated on allow_cache. A pending-backend-validation pass calls fetch_current_user_cached(.., allow_cache=false), which bypasses suppression, goes to the network, and calls clear_current_user_failure() on success. Any login that force-validates self-heals on its first fetch.
Even in the exposed case the caller already falls back to the stored snapshot on Err, so the outcome is a stale-for-up-to-60s answer, not a failed one.
That leaves the real exposure as a session-preserving re-login that reuses the existing JWT and skips live validation — an embedder or a sign-out/sign-in flow that keeps the token.
Fix shape
One reset in app_state that clears both globals, called from clear_session. Fixing only the negative cache would leave a structurally identical bug one line away.
Provenance
Found by the Codex reviewer on #5742, which added CURRENT_USER_FAILURE and its reset. That PR placed the negative reset exactly where the existing positive one already lived, so it did not introduce the asymmetry — the gap predates it and is deliberately not folded into it: the fix reaches into security/credentials/ops.rs from a PR about fetch backoff, which would make it harder to review and harder to revert. Discussion: #5742 (comment)
Verified — accurate, and still unfixed on main (6be58a9)
Re-checked every claim. All of them hold; the files have since been split, so here are the current locations.
1. clear_session still touches neither cache. It now lives at src/openhuman/security/credentials/ops_part_02.rs:84-158. It does exactly what the issue lists — flips the scheduler gate, remove_profile, disconnects the socket, clear_workflow_bridge(), clears active_user.toml, stop_login_gated_services(), rebinds the core context to the signed-out workspace, sentry_scope::clear() — and then returns. No write to either global.
2. The only reset site is still the deferred-rejection path, not logout. Now src/openhuman/desktop/app_state/ops_part_02.rs:141-143, inside clear_deferred_session_after_backend_rejection() (declared at ops_part_02.rs:122):
3. Both globals are where the issue says.CURRENT_USER_CACHE at ops_part_01.rs:56, CURRENT_USER_FAILURE at ops_part_01.rs:65, clear_current_user_failure() at ops_part_01.rs:251.
One update: it is three globals now, not two
LAST_CURRENT_USER_SUCCESS (src/openhuman/desktop/app_state/ops_staleness.rs:31) landed with #5930 after this was filed. It is the same shape — process-global, keyed on (api_base, token), reset only from clear_deferred_session_after_backend_rejection (the clear_current_user_success() call above). clear_session does not clear it either.
What makes it worth noting is that its own doc comment already documents this issue as a constraint it had to design around (ops_staleness.rs:25-31):
Keyed on (api_base, token) like both caches. An unkeyed timestamp would outlive the identity that earned it: the ordinary credentials::clear_session logout does not run the deferred-rejection cleanup, so a logout and re-login in the same process — or simply a second identity failing after a first succeeded — would report the previous session's age as this one's.
So the gap has already shaped a second piece of code rather than being fixed — which is the concrete version of this issue's own warning that fixing one cache "would leave a structurally identical bug one line away." It is now two lines away, and the key-on-token mitigation is exactly the thing the same-JWT re-login case defeats.
The bounds in the issue still hold
Re-confirmed against the current code: the (api_base, token) key check means a different token misses all three; allow_cache=false on a force-validating pass bypasses the negative cache and calls clear_current_user_failure() on success; and the caller falls back to the stored snapshot on Err, so the worst outcome is stale-for-up-to-60s rather than a failed session. The exposure remains a session-preserving re-login that reuses the JWT and skips live validation.
Status
PR #5822 ("fix(auth): drop every current-user cache on sign-out") is open and is the right shape — it is broader than the fix sketched here, and correctly so. It adds ops_current_user_generation.rs, whose opening comment names the gap the issue's "one reset called from clear_session" version would still leave:
CURRENT_USER_CACHE, CURRENT_USER_FAILURE and LAST_CURRENT_USER_SUCCESS are all keyed on (api_base, token), so clearing them is not enough on its own: fetch_current_user_cached awaits the network between reading them and writing them, and a refresh already in flight when sign-out lands would re-publish exactly the state the sign-out removed.
A generation counter read with the token and re-checked under each cache's lock closes that race. All three globals are covered.
(The earlier PR #5774, "drop both current-user caches on sign-out", is closed — superseded by #5822, which handles the third global and the in-flight refresh.)
Activity
fix(auth): drop both current-user caches on sign-out (tinyhumansai#5758)
fix(auth): drop both current-user caches on sign-out (tinyhumansai#5758)
fix(auth): drop every current-user cache on sign-out (tinyhumansai#5758)
YellowSnnowmann commentedon Sep 11, 2026
Verified — accurate, and still unfixed on
main(6be58a9)Re-checked every claim. All of them hold; the files have since been split, so here are the current locations.
1.
clear_sessionstill touches neither cache. It now lives atsrc/openhuman/security/credentials/ops_part_02.rs:84-158. It does exactly what the issue lists — flips the scheduler gate,remove_profile, disconnects the socket,clear_workflow_bridge(), clearsactive_user.toml,stop_login_gated_services(), rebinds the core context to the signed-out workspace,sentry_scope::clear()— and then returns. No write to either global.2. The only reset site is still the deferred-rejection path, not logout. Now
src/openhuman/desktop/app_state/ops_part_02.rs:141-143, insideclear_deferred_session_after_backend_rejection()(declared atops_part_02.rs:122):3. Both globals are where the issue says.
CURRENT_USER_CACHEatops_part_01.rs:56,CURRENT_USER_FAILUREatops_part_01.rs:65,clear_current_user_failure()atops_part_01.rs:251.One update: it is three globals now, not two
LAST_CURRENT_USER_SUCCESS(src/openhuman/desktop/app_state/ops_staleness.rs:31) landed with #5930 after this was filed. It is the same shape — process-global, keyed on(api_base, token), reset only fromclear_deferred_session_after_backend_rejection(theclear_current_user_success()call above).clear_sessiondoes not clear it either.What makes it worth noting is that its own doc comment already documents this issue as a constraint it had to design around (
ops_staleness.rs:25-31):So the gap has already shaped a second piece of code rather than being fixed — which is the concrete version of this issue's own warning that fixing one cache "would leave a structurally identical bug one line away." It is now two lines away, and the key-on-token mitigation is exactly the thing the same-JWT re-login case defeats.
The bounds in the issue still hold
Re-confirmed against the current code: the
(api_base, token)key check means a different token misses all three;allow_cache=falseon a force-validating pass bypasses the negative cache and callsclear_current_user_failure()on success; and the caller falls back to the stored snapshot onErr, so the worst outcome is stale-for-up-to-60s rather than a failed session. The exposure remains a session-preserving re-login that reuses the JWT and skips live validation.Status
PR #5822 ("fix(auth): drop every current-user cache on sign-out") is open and is the right shape — it is broader than the fix sketched here, and correctly so. It adds
ops_current_user_generation.rs, whose opening comment names the gap the issue's "one reset called fromclear_session" version would still leave:A generation counter read with the token and re-checked under each cache's lock closes that race. All three globals are covered.
(The earlier PR #5774, "drop both current-user caches on sign-out", is closed — superseded by #5822, which handles the third global and the in-flight refresh.)
Leaving this open until #5822 merges.
Merge pull request #5822 from ntdatt812/fix/5758-clear-session-user-c…
fix(auth): drop every current-user cache on sign-out (tinyhumansai#5758…
Merge pull request tinyhumansai#5822 from ntdatt812/fix/5758-clear-se…