A session cookie is supposed to belong to exactly one browser. CVE-2023-30861 is a reminder of how easily that guarantee breaks down once a caching layer sits between your users and your application. The flaw affects Flask, one of the most widely used Python web frameworks, and it allows a session cookie issued to one user to be cached and replayed to a completely different user — no exploit chain, no injection, just a missing HTTP header and a caching proxy doing exactly what it was configured to do.
What the vulnerability actually is
Flask's default session interface (SecureCookieSessionInterface) signs and stores session data client-side in a cookie set via the Set-Cookie header. Under certain conditions — most notably when SESSION_REFRESH_EACH_REQUEST is enabled (the default) and a route doesn't explicitly vary its response by cookie — Flask can send a response containing a user's session cookie without also sending a Vary: Cookie header.
That header matters more than its terse name suggests. Vary: Cookie tells any intermediary cache (a CDN, a reverse proxy like Varnish or Nginx configured for caching, or even certain browser and corporate proxy caches) that the response body and headers depend on the value of the request's Cookie header, and therefore must not be served to a different client without revalidation. Without it, a caching layer that's configured to cache based on URL alone may store the entire response — session cookie included — and then hand that exact response, Set-Cookie header and all, to the next visitor who requests the same URL.
The practical effect: User A visits a page, gets a session cookie in the response, and a caching proxy stores that response. User B requests the same page shortly after, and instead of getting their own session, they receive User A's cached response — including User A's Set-Cookie header. If User B's browser accepts that cookie, User B's browser is now operating with User A's session. Depending on what that session grants access to, this can range from a privacy leak to outright account takeover, entirely without either user doing anything wrong.
This is not a theoretical caching edge case. Sites fronted by a CDN, an API gateway with response caching enabled, or a shared reverse proxy are the exact deployment pattern this bug targets. The more aggressively a deployment caches full-page responses to improve performance, the more exposed it is.
Affected versions and components
The issue lives in Flask's core session-handling code, specifically the logic that decides whether to emit Vary: Cookie alongside Set-Cookie. It affects Flask versions prior to the patched releases; the Flask maintainers shipped fixes in Flask 2.3.2 on the mainline and backported the fix to Flask 2.2.5 for teams still on the 2.2.x branch. If your dependency manifest resolves to a Flask version older than 2.2.5, or to a 2.3.x release prior to 2.3.2, you should treat the deployment as exposed until you've confirmed otherwise.
Because Flask underpins a large share of the Python web ecosystem — directly and as a transitive dependency of frameworks and toolkits built on top of it — the practical exposure extends well beyond applications that call Flask APIs directly. Any service, internal tool, or vendored component that pulls in an affected Flask version inherits the weakness, whether or not the engineering team maintaining that service is even aware Flask is in the dependency tree.
The vulnerability was published as CVE-2023-30861, with the corresponding GitHub Security Advisory tracked against the Pallets flask repository.
CVSS, EPSS, and KEV context
Public scoring for this advisory reflects a network-exploitable, no-privileges-required, no-user-interaction issue with a high confidentiality impact and no integrity or availability impact — consistent with the nature of the bug, which discloses session state rather than allowing an attacker to modify data or take the application down. That combination of factors pushes the severity into the high range under CVSS v3.1. Treat it as significant for any deployment that sits behind a caching layer and stores meaningful authorization state in the session cookie, even if the exact published score varies slightly across scoring sources.
This CVE has not been added to CISA's Known Exploited Vulnerabilities (KEV) catalog, and it has not been associated with the kind of mass, opportunistic exploitation that drives high EPSS scores. That's consistent with the mechanics of the bug: it requires a specific deployment shape — a caching proxy or CDN sitting in front of a vulnerable Flask app, actually caching responses that carry session cookies — rather than being remotely triggerable against any Flask install by default. A low EPSS score and absence from KEV should not be read as "low priority," though. It means opportunistic mass scanning hasn't been able to target it broadly, not that a targeted attacker who has mapped out your infrastructure couldn't exploit it easily once they identify a caching layer in front of your application.
Timeline
- Flask's session interface shipped without a
Vary: Cookiesafeguard for the affected code path across multiple release lines. - The issue was reported to the Pallets project (Flask's maintaining organization) and triaged as a security advisory.
- Pallets shipped fixed releases — Flask 2.3.2 on the current release line, with a backport to Flask 2.2.5 for the prior stable line — adding logic to set
Vary: Cookiewhen the session may have been modified or accessed during the request. - The fix was published alongside a public GitHub Security Advisory and assigned CVE-2023-30861, with NVD publishing its own corresponding record.
- Downstream Linux distributions and language package registries picked up the patched Flask releases in the weeks that followed.
Remediation steps
- Upgrade Flask. Move to Flask 2.3.2 or later, or Flask 2.2.5 if you must remain on the 2.2.x line. Check
pip show flaskor your lockfile (requirements.txt,Pipfile.lock,poetry.lock) to confirm the resolved version, not just the version pinned in your top-level manifest — transitive pins can quietly keep you on a vulnerable release. - Audit your caching layer independently of the Flask upgrade. Even after patching, review CDN and reverse-proxy configuration to confirm it honors
Varyheaders and does not cache responses that carry aSet-Cookieheader for session cookies. Defense in depth matters here: a misconfigured cache can reintroduce a similar exposure through other headers or application logic. - Disable caching for session-bearing routes where possible. For any endpoint that sets or reads session state, explicitly set
Cache-Control: private, no-store(or an equivalent directive) rather than relying solely onVary: Cookieto prevent caching. - Rotate session secrets and review logs for signs of cross-session data exposure if you were running an affected Flask version behind a caching proxy in production, particularly if cache-hit logs show hits on authenticated routes.
- Treat Flask as a first-class entry in your SBOM. Track it — and any framework built on top of it — explicitly, rather than letting it sit as an implicit transitive dependency you only discover when something breaks.
- Re-test after upgrading. Confirm via response headers (
curl -Iagainst an authenticated route) thatVary: Cookieis now present when a session is active, and that your cache actually respects it in a staging environment that mirrors production caching behavior.
How Safeguard Helps
CVE-2023-30861 is a textbook example of why supply chain visibility has to go deeper than "is this dependency present." The bug is dangerous specifically at the intersection of a library version and an infrastructure pattern — Flask plus a caching proxy — and that's exactly the kind of context a bare CVE feed or a point-in-time dependency scan tends to miss.
Safeguard's software supply chain platform is built to close that gap:
- Continuous SBOM-based monitoring identifies every service, internal tool, and vendored component resolving to an affected Flask version — including transitive dependencies buried several layers deep, where teams are least likely to notice a vulnerable pin.
- CI/CD policy gates can block builds and merges that would introduce or retain a known-vulnerable Flask version, catching the issue before it ever reaches a caching-enabled production environment.
- Contextual risk scoring weighs factors beyond the raw CVSS number — such as whether a flagged service actually sits behind a CDN or reverse-proxy cache — so security teams can prioritize the deployments where this specific bug is actually exploitable, instead of treating every Flask instance as equally urgent.
- Exploitability and threat-intel enrichment, including EPSS trends and KEV status, keeps this advisory (and the thousands like it in a typical dependency tree) ranked accurately as new exploitation data emerges, rather than relying on a static severity label assigned at disclosure time.
- Remediation tracking verifies that patched Flask versions actually reach production, not just that a pull request was opened, closing the loop between detection and a confirmed fix.
Session-handling bugs like this one rarely announce themselves with a dramatic proof-of-concept — they sit quietly in infrastructure until the right caching configuration turns a missing header into a real breach. Safeguard is built to surface exactly that kind of latent risk across your entire software supply chain before an attacker finds it first.