Repository navigation
fix(cache): bypass the backend for requests that arrived with a session - #338
Merged
Merged
Conversation
Only Authorization bypassed the shared backend, so a plain @cache on a route that read the session served the first visitor's response to every other one: tokens in X-Session-Token or the session cookie, and anonymous sessions, were not recognised. A request now bypasses the backend and is answered with private when the session middleware loaded a session for it or request.session is non-empty; a token that resolves to no session does not count. public=True and cache_authorized=True lift it as before. Closes #319
This was referenced Sep 28, 2026
@cache: Authorization requests get a 0% hit rate on public routes and only a DEBUG log says why
#326
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #319.
Problem
Since #296, only a request with
Authorizationbypasses the shared backend. A session token sent asX-Session-Tokenor as the session cookie did not. So a plain@cache(ttl=...)on a route that reads the session served the first visitor's response to everyone. WithAuthenticatedSession, bob got alice's/whoami. With an anonymous session, alice's cart reached bob and a visitor with no cookie at all.Change
@cachenow treats a request as belonging to one caller when any of these holds:Authorization(unchanged);request.state.__fastapi_cachex_session). This covers all three transports (header, bearer, cookie) and both user and anonymous sessions;request.sessionis non-empty under any session middleware, Starlette's cookie sessions included.Such a request takes the existing
Authorizationpath: the backend is neither read nor written, ETag revalidation still runs against the fresh render, and the response goes out withprivatein place ofpublic.public=Trueandcache_authorized=Truelift the bypass exactly as they do forAuthorization. The debug log now names what triggered the bypass.A token that resolves to no session (forged, expired) does not count. That way, random tokens cannot be used to push traffic past the cache.
Behaviour change: on an app where every browser holds a session (for example a CSRF value in
request.session),@cacheroutes withoutpublic=Truenow answer those requests uncached. That is the safe default, since the handler may read the session. A route whose response does not depend on the session should say so withpublic=True.Docs
Authorizationone.@cache.changelog.d/319.security.md.Tests
tests/session/test_cache_session_bypass.pyhas 12 tests. Five of them fail on master:The other seven guard against over-reaching:
public=Trueis shared across sessions;cache_authorized=Truewith a per-user key caches per user;Full suite: 1046 passed, 203 skipped, coverage 93.87%. ruff, mypy strict and pre-commit are clean.