summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Howard <git@theorangeone.net>2026-05-12 16:29:56 +0100
committerNatalia <124304+nessita@users.noreply.github.com>2026-06-03 08:37:26 -0300
commitd618d7ae4fec727d5b582bd24f803c28d17bf7cd (patch)
tree51fdf6600a8756f7c9065bfe8c81b91a696f2117
parentdf887f50198593a0e5b4638bfddbbd43a30fd276 (diff)
Fixed CVE-2026-8404 -- Used Cache-Control directives case-insensitively in UpdateCacheMiddleware.
Thanks Ahmed Badawe for the report, and Jacob Walls for reviews.
-rw-r--r--django/middleware/cache.py4
-rw-r--r--docs/releases/5.2.15.txt16
-rw-r--r--docs/releases/6.0.6.txt16
-rw-r--r--tests/cache/tests.py16
4 files changed, 44 insertions, 8 deletions
diff --git a/django/middleware/cache.py b/django/middleware/cache.py
index 678fde68a8..e70f427051 100644
--- a/django/middleware/cache.py
+++ b/django/middleware/cache.py
@@ -102,8 +102,8 @@ class UpdateCacheMiddleware(MiddlewareMixin):
# Don't cache responses when the Cache-Control header is set to
# private, no-cache, or no-store.
- cache_control = response.get("Cache-Control", ())
- if any(
+ cache_control = response.get("Cache-Control", "").lower()
+ if cache_control and any(
directive in cache_control
for directive in (
"private",
diff --git a/docs/releases/5.2.15.txt b/docs/releases/5.2.15.txt
index 14796f8581..c068600280 100644
--- a/docs/releases/5.2.15.txt
+++ b/docs/releases/5.2.15.txt
@@ -35,3 +35,19 @@ Connections configured with :setting:`EMAIL_USE_SSL` are not affected.
This issue has severity "low" according to the :ref:`Django security policy
<severity-levels>`.
+
+CVE-2026-8404: Potential exposure of private data via case-sensitive ``Cache-Control`` directives
+=================================================================================================
+
+:class:`~django.middleware.cache.UpdateCacheMiddleware` and
+:func:`~django.views.decorators.cache.cache_page` incorrectly cached responses
+marked with private ``Cache-Control`` directives when using mixed or uppercase
+values (e.g. ``Private``).
+
+The :func:`~django.views.decorators.cache.cache_control` decorator and
+:func:`~django.utils.cache.patch_cache_control` function were not affected,
+since they normalize directives to lowercase. This issue only affects responses
+where ``Cache-Control`` is set manually.
+
+This issue has severity "low" according to the :ref:`Django security policy
+<severity-levels>`.
diff --git a/docs/releases/6.0.6.txt b/docs/releases/6.0.6.txt
index 20ae1db93c..afcbbe0eb2 100644
--- a/docs/releases/6.0.6.txt
+++ b/docs/releases/6.0.6.txt
@@ -37,6 +37,22 @@ Connections configured with :setting:`EMAIL_USE_SSL` are not affected.
This issue has severity "low" according to the :ref:`Django security policy
<severity-levels>`.
+CVE-2026-8404: Potential exposure of private data via case-sensitive ``Cache-Control`` directives
+=================================================================================================
+
+:class:`~django.middleware.cache.UpdateCacheMiddleware` and
+:func:`~django.views.decorators.cache.cache_page` incorrectly cached responses
+marked with private ``Cache-Control`` directives when using mixed or uppercase
+values (e.g. ``Private``).
+
+The :func:`~django.views.decorators.cache.cache_control` decorator and
+:func:`~django.utils.cache.patch_cache_control` function were not affected,
+since they normalize directives to lowercase. This issue only affects responses
+where ``Cache-Control`` is set manually.
+
+This issue has severity "low" according to the :ref:`Django security policy
+<severity-levels>`.
+
Bugfixes
========
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index 65ca885125..d4cfadb050 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -2851,15 +2851,19 @@ class CacheMiddlewareTest(SimpleTestCase):
Responses with 'Cache-Control: private/no-cache/no-store' are
not cached.
"""
- for cc in ("private", "no-cache", "no-store"):
+ for cc in ("private", "no-cache", "no-store", "PRIVATE", "NO-store"):
with self.subTest(cache_control=cc):
- view_with_cache = cache_page(3)(
- cache_control(**{cc: True})(hello_world_view)
- )
+ # Cannot use @cache_control() as it lowercases directives.
+ @cache_page(3)
+ def view(request, value):
+ return HttpResponse(
+ f"Hello World {value}", headers={"Cache-Control": cc}
+ )
+
request = self.factory.get("/view/")
- response = view_with_cache(request, "1")
+ response = view(request, "1")
self.assertEqual(response.content, b"Hello World 1")
- response = view_with_cache(request, "2")
+ response = view(request, "2")
self.assertEqual(response.content, b"Hello World 2")
def test_vary_asterisk_not_cached(self):