summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2026-04-23 14:53:28 +0200
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2026-05-05 14:34:21 +0200
commitc79bdfc1351ef2a2ad95df36241a74c736ef20a1 (patch)
tree090a85d5f277d518164973b7bafb9d8ec92a870b
parent7f6e9b55130d5158804c0acbc0b24ccb7422ed82 (diff)
Fixed CVE-2026-6907 -- Prevented caching of requests when Vary header contains an asterisk.
Thank you Ahmad Sadeddin for the report and Jacob Walls for the review.
-rw-r--r--django/middleware/cache.py4
-rw-r--r--docs/releases/5.2.14.txt10
-rw-r--r--docs/releases/6.0.5.txt10
-rw-r--r--tests/cache/tests.py25
4 files changed, 49 insertions, 0 deletions
diff --git a/django/middleware/cache.py b/django/middleware/cache.py
index 8c42a52b4a..678fde68a8 100644
--- a/django/middleware/cache.py
+++ b/django/middleware/cache.py
@@ -113,6 +113,10 @@ class UpdateCacheMiddleware(MiddlewareMixin):
):
return response
+ # Don't cache responses when the Vary header contains '*'.
+ if has_vary_header(response, "*"):
+ return response
+
# Page timeout takes precedence over the "max-age" and the default
# cache timeout.
timeout = self.page_timeout
diff --git a/docs/releases/5.2.14.txt b/docs/releases/5.2.14.txt
index cefa9a1f5b..1fe882ebea 100644
--- a/docs/releases/5.2.14.txt
+++ b/docs/releases/5.2.14.txt
@@ -30,3 +30,13 @@ a cached public page.
This issue has severity "low" according to the :ref:`Django security policy
<security-disclosure>`.
+
+CVE-2026-6907: Potential exposure of private data due to incorrect handling of ``Vary: *`` in ``UpdateCacheMiddleware``
+=======================================================================================================================
+
+Previously, :class:`~django.middleware.cache.UpdateCacheMiddleware` would
+erroneously cache requests where the ``Vary`` header contained an asterisk
+(``'*'``). This could lead to private data being stored and served.
+
+This issue has severity "low" according to the :ref:`Django security policy
+<security-disclosure>`.
diff --git a/docs/releases/6.0.5.txt b/docs/releases/6.0.5.txt
index 8e7aca7158..08bce66e6a 100644
--- a/docs/releases/6.0.5.txt
+++ b/docs/releases/6.0.5.txt
@@ -32,6 +32,16 @@ a cached public page.
This issue has severity "low" according to the :ref:`Django security policy
<security-disclosure>`.
+CVE-2026-6907: Potential exposure of private data due to incorrect handling of ``Vary: *`` in ``UpdateCacheMiddleware``
+=======================================================================================================================
+
+Previously, :class:`~django.middleware.cache.UpdateCacheMiddleware` would
+erroneously cache requests where the ``Vary`` header contained an asterisk
+(``'*'``). This could lead to private data being stored and served.
+
+This issue has severity "low" according to the :ref:`Django security policy
+<security-disclosure>`.
+
Bugfixes
========
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index a94a8a63cd..65ca885125 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -2616,6 +2616,18 @@ def hello_world_view(request, value):
return HttpResponse("Hello World %s" % value)
+def hello_world_view_patch_vary_headers_asterisk(request, value):
+ response = HttpResponse("Hello World %s" % value)
+ patch_vary_headers(response, ("*",))
+ return response
+
+
+def hello_world_view_vary_headers_includes_asterisk(request, value):
+ response = HttpResponse("Hello World %s" % value)
+ response["Vary"] = "Cookie, *, Pony"
+ return response
+
+
def csrf_view(request):
return HttpResponse(csrf(request)["csrf_token"])
@@ -2850,6 +2862,19 @@ class CacheMiddlewareTest(SimpleTestCase):
response = view_with_cache(request, "2")
self.assertEqual(response.content, b"Hello World 2")
+ def test_vary_asterisk_not_cached(self):
+ views_with_cache = (
+ cache_page(3)(hello_world_view_patch_vary_headers_asterisk),
+ cache_page(3)(hello_world_view_vary_headers_includes_asterisk),
+ )
+ for view in views_with_cache:
+ with self.subTest(view=view):
+ request = self.factory.get("/view/")
+ response = view(request, "1")
+ self.assertEqual(response.content, b"Hello World 1")
+ response = view(request, "2")
+ self.assertEqual(response.content, b"Hello World 2")
+
def test_sensitive_cookie_not_cached(self):
"""
Django must prevent caching of responses that set a user-specific (and