diff options
| author | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2026-04-23 14:53:28 +0200 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2026-05-05 14:34:21 +0200 |
| commit | c79bdfc1351ef2a2ad95df36241a74c736ef20a1 (patch) | |
| tree | 090a85d5f277d518164973b7bafb9d8ec92a870b | |
| parent | 7f6e9b55130d5158804c0acbc0b24ccb7422ed82 (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.py | 4 | ||||
| -rw-r--r-- | docs/releases/5.2.14.txt | 10 | ||||
| -rw-r--r-- | docs/releases/6.0.5.txt | 10 | ||||
| -rw-r--r-- | tests/cache/tests.py | 25 |
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 |
