Opened 10 months ago

Closed 10 months ago

Last modified 10 days ago

#36560 closed Cleanup/optimization (fixed)

When the Cache-Control header is set to no-store, the response is cached.

Reported by: mengxun Owned by: mengxun
Component: HTTP handling Version: 5.2
Severity: Normal Keywords: cache
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: yes UI/UX: no

Description (last modified by mengxun)

settings.py

...
MIDDLEWARE = [
    "django.middleware.cache.UpdateCacheMiddleware", 
    "django.middleware.common.CommonMiddleware",
    "django.middleware.cache.FetchFromCacheMiddleware", 
]
...

views.py

from django.http import HttpResponse
import datetime

def cache_demo(request):
    resp = HttpResponse(f"Current content at {datetime.datetime.now()}")
    resp["Cache-Control"] = "no-store"
    return resp

Based on the example above, when the Cache-Control header is set to no-store, the response content is still cached.

Change History (13)

comment:1 by mengxun, 10 months ago

Description: modified (diff)

comment:2 by Sarah Boyce, 10 months ago

Component: Core (Cache system)HTTP handling
Needs tests: set
Triage Stage: UnreviewedAccepted
Type: BugCleanup/optimization

Similar to #28833, can work around by using @never_cache

comment:3 by Jagadesha NH, 10 months ago

Owner: set to Jagadesha NH
Status: newassigned

I can work on these changes

comment:4 by Jagadesha NH, 10 months ago

Owner: Jagadesha NH removed
Status: assignednew

Seems like, someone has already raised a PR

comment:5 by Sarah Boyce, 10 months ago

Owner: set to mengxun
Status: newassigned

comment:6 by Sarah Boyce, 10 months ago

Has patch: set

comment:7 by Sarah Boyce, 10 months ago

Needs tests: unset
Triage Stage: AcceptedReady for checkin

comment:8 by Sarah Boyce <42296566+sarahboyce@…>, 10 months ago

Resolution: fixed
Status: assignedclosed

In ed7c1a56:

Fixed #36560 -- Prevented UpdateCacheMiddleware from caching responses with Cache-Control 'no-cache' or 'no-store'.

comment:9 by Natalia <124304+nessita@…>, 4 weeks ago

In 366d9ae:

[5.2.x] Fixed CVE-2026-8404 -- Used Cache-Control directives case-insensitively in UpdateCacheMiddleware.

Thanks Ahmed Badawe for the report, and Jacob Walls for reviews.

This commit includes:

[5.2.x] Fixed #36560 -- Prevented UpdateCacheMiddleware from caching responses with Cache-Control 'no-cache' or 'no-store'.

Backport of ed7c1a56400d64f109f30df3ce697984cdad7c75 from main.

Backport of d618d7ae4fec727d5b582bd24f803c28d17bf7cd from main.

comment:10 by Jacob Walls <jacobtylerwalls@…>, 3 weeks ago

In 142b881:

Refs #36560, CVE-2026-35193 -- Replaced substring check on cache-control directives in UpdateCacheMiddleware.

Avoid false positives from hypothetical extension directives
that could be superstrings of the ones we are checking.

comment:11 by Jacob Walls <jacobtylerwalls@…>, 3 weeks ago

In b615947:

[6.1.x] Refs #36560, CVE-2026-35193 -- Replaced substring check on cache-control directives in UpdateCacheMiddleware.

Avoid false positives from hypothetical extension directives
that could be superstrings of the ones we are checking.

Backport of 142b881cecaddc334cabec139e701c0e4b9798da from main.

comment:12 by nessita <124304+nessita@…>, 10 days ago

In b461519b:

Refs #36560, CVE-2026-35193 -- Recognized qualified cache-control directives.

The switch from substring matching to exact token membership in
142b881cecaddc334cabec139e701c0e4b9798da caused qualified directive
forms permitted by RFC 9111 (e.g. Cache-Control: private="Set-Cookie")
to be missed, allowing such responses to be stored in a shared cache.

This work added a new split_directive_names() helper that yields the
lowercased directive name from each token, dropping any qualified value
and stripping whitespace around "=", so qualified forms reduce to their
directive name. UpdateCacheMiddleware now uses it so private,
no-cache, and no-store (and the public exception for
Authorization) match regardless of qualified form.

Aligned ConditionalGetMiddleware.needs_etag() to use the same helper,
since it relied on the same brittle exact-token check. Sharing one
helper keeps the two directive lookups consistent and means malformed
input (e.g. no-store="x") now correctly suppresses the ETag instead
of being silently ignored.

Also stripped whitespace around = in patch_cache_control's directive
parsing so a qualified directive with stray whitespace is still recognized.

Thanks to Jacob Walls for reviews.

comment:13 by Natalia <124304+nessita@…>, 10 days ago

In 27f74d0:

[6.1.x] Refs #36560, CVE-2026-35193 -- Recognized qualified cache-control directives.

The switch from substring matching to exact token membership in
142b881cecaddc334cabec139e701c0e4b9798da caused qualified directive
forms permitted by RFC 9111 (e.g. Cache-Control: private="Set-Cookie")
to be missed, allowing such responses to be stored in a shared cache.

This work added a new split_directive_names() helper that yields the
lowercased directive name from each token, dropping any qualified value
and stripping whitespace around "=", so qualified forms reduce to their
directive name. UpdateCacheMiddleware now uses it so private,
no-cache, and no-store (and the public exception for
Authorization) match regardless of qualified form.

Aligned ConditionalGetMiddleware.needs_etag() to use the same helper,
since it relied on the same brittle exact-token check. Sharing one
helper keeps the two directive lookups consistent and means malformed
input (e.g. no-store="x") now correctly suppresses the ETag instead
of being silently ignored.

Also stripped whitespace around = in patch_cache_control's directive
parsing so a qualified directive with stray whitespace is still recognized.

Thanks to Jacob Walls for reviews.

Backport of b461519bf5973d7fc149560d2f99acdba71a437d from main.

Note: See TracTickets for help on using tickets.
Back to Top