Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-Django4 for openSUSE:Factory checked in at 2026-05-06 19:19:31 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-Django4 (Old) and /work/SRC/openSUSE:Factory/.python-Django4.new.30200 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-Django4" Wed May 6 19:19:31 2026 rev:5 rq:1351152 version:4.2.30 Changes: -------- --- /work/SRC/openSUSE:Factory/python-Django4/python-Django4.changes 2026-04-09 16:22:42.029467326 +0200 +++ /work/SRC/openSUSE:Factory/.python-Django4.new.30200/python-Django4.changes 2026-05-06 19:23:35.287167756 +0200 @@ -1,0 +2,14 @@ +Wed May 6 09:24:41 UTC 2026 - Markéta Machová <[email protected]> + +- End of upstream support, hence adding security patches: + * CVE-2026-5766: Potential denial-of-service vulnerability in ASGI + requests via file upload limit bypass (bsc#1264153) + * CVE-2026-5766.patch + * CVE-2026-35192: Session fixation via public cached pages and + SESSION_SAVE_EVERY_REQUEST (bsc#1264154) + * CVE-2026-35192.patch + * CVE-2026-6907: Potential exposure of private data due to incorrect + handling of Vary: * in UpdateCacheMiddleware (bsc#1264152) + * CVE-2026-6907.patch + +------------------------------------------------------------------- New: ---- CVE-2026-35192.patch CVE-2026-5766.patch CVE-2026-6907.patch ----------(New B)---------- New: SESSION_SAVE_EVERY_REQUEST (bsc#1264154) * CVE-2026-35192.patch * CVE-2026-6907: Potential exposure of private data due to incorrect New: requests via file upload limit bypass (bsc#1264153) * CVE-2026-5766.patch * CVE-2026-35192: Session fixation via public cached pages and New: handling of Vary: * in UpdateCacheMiddleware (bsc#1264152) * CVE-2026-6907.patch ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-Django4.spec ++++++ --- /var/tmp/diff_new_pack.sNVD5q/_old 2026-05-06 19:23:36.415214290 +0200 +++ /var/tmp/diff_new_pack.sNVD5q/_new 2026-05-06 19:23:36.419214455 +0200 @@ -59,6 +59,12 @@ # PATCH-FIX-UPSTREAM https://github.com/django/django/commit/34066d6cf3d66b8a3c7fac86912455dbb2ed0ed6 Refs #35844 -- Fixed tests for test --parallel option on Python 3.14+. # PATCH-FIX-UPSTREAM https://github.com/django/django/commit/fcd9d08379a2aee3b2c49eab0d0b8db6fd66d091 Refs #35844 -- Fixed OtherModelFormTests.test_prefetch_related_queryset() test on Python 3.14+. Patch8: py314.patch +# PATCH-FIX-UPSTREAM CVE-2026-5766.patch bsc#1264153 +Patch9: CVE-2026-5766.patch +# PATCH-FIX-UPSTREAM CVE-2026-35192.patch bsc#1264154 +Patch10: CVE-2026-35192.patch +# PATCH-FIX-UPSTREAM CVE-2026-6907.patch bsc#1264152 +Patch11: CVE-2026-6907.patch BuildRequires: %{python_module Jinja2 >= 2.9.2} BuildRequires: %{python_module Pillow >= 6.2.0} BuildRequires: %{python_module PyYAML} ++++++ CVE-2026-35192.patch ++++++ >From 47cf968c125e3fab317e10fe150ec479e745f995 Mon Sep 17 00:00:00 2001 From: Jake Howard <[email protected]> Date: Wed, 1 Apr 2026 18:21:30 +0200 Subject: [PATCH] [5.2.x] Fixed CVE-2026-35192 -- Ensured Vary header is sent when setting session cookie with SESSION_SAVE_EVERY_REQUEST=True. Thank you Jacob Walls and Natalia Bidart for reviews. Backport of 7f6e9b55130d5158804c0acbc0b24ccb7422ed82 from main. --- django/contrib/sessions/middleware.py | 11 ++++++++--- docs/releases/5.2.14.txt | 11 +++++++++++ tests/sessions_tests/tests.py | 28 +++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py index 9c934f9dddab..c6a5e336788c 100644 --- a/django/contrib/sessions/middleware.py +++ b/django/contrib/sessions/middleware.py @@ -40,10 +40,11 @@ def process_response(self, request, response): domain=settings.SESSION_COOKIE_DOMAIN, samesite=settings.SESSION_COOKIE_SAMESITE, ) - patch_vary_headers(response, ("Cookie",)) + need_vary_cookie = True else: - if accessed: - patch_vary_headers(response, ("Cookie",)) + # If the session was accessed, it must be varied on, regardless of + # whether it was modified or will be saved. + need_vary_cookie = accessed if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty: if request.session.get_expire_at_browser_close(): max_age = None @@ -74,4 +75,8 @@ def process_response(self, request, response): httponly=settings.SESSION_COOKIE_HTTPONLY or None, samesite=settings.SESSION_COOKIE_SAMESITE, ) + # With a session cookie set, it must be varied on. + need_vary_cookie = True + if need_vary_cookie: + patch_vary_headers(response, ("Cookie",)) return response diff --git a/tests/sessions_tests/tests.py b/tests/sessions_tests/tests.py index 9eabb933a8ab..4d09b86c9df6 100644 --- a/tests/sessions_tests/tests.py +++ b/tests/sessions_tests/tests.py @@ -1021,6 +1021,7 @@ def test_secure_session_cookie(self): # Handle the response through the middleware response = middleware(request) self.assertIs(response.cookies[settings.SESSION_COOKIE_NAME]["secure"], True) + self.assertEqual(response.headers["Vary"], "Cookie") @override_settings(SESSION_COOKIE_HTTPONLY=True) def test_httponly_session_cookie(self): @@ -1161,6 +1162,7 @@ def response_ending_session(request): ), str(response.cookies[settings.SESSION_COOKIE_NAME]), ) + self.assertEqual(response.headers["Vary"], "Cookie") def test_flush_empty_without_session_cookie_doesnt_set_cookie(self): def response_ending_session(request): @@ -1178,6 +1180,32 @@ def response_ending_session(request): # The session is accessed so "Vary: Cookie" should be set. self.assertEqual(response.headers["Vary"], "Cookie") + @override_settings(SESSION_SAVE_EVERY_REQUEST=True) + def test_save_every_request_with_non_empty_session_renews_session_cookie(self): + request = self.request_factory.get("/") + middleware = SessionMiddleware(self.get_response_touching_session) + + # Make sure the request has a session. + middleware(request) + + # A cookie should be set. + self.assertIs(request.session.is_empty(), False) + self.assertEqual(request.session["hello"], "world") + + request.COOKIES[settings.SESSION_COOKIE_NAME] = request.session.session_key + + def simple_view(request): + return HttpResponse("Session test") + + middleware = SessionMiddleware(simple_view) + response = middleware(request) + + # A cookie should be set because SESSION_SAVE_EVERY_REQUEST=True, + # even though the session wasn't touched. + self.assertIn(settings.SESSION_COOKIE_NAME, response.cookies) + # There's a session, so also Vary on it. + self.assertEqual(response.headers["Vary"], "Cookie") + def test_empty_session_saved(self): """ If a session is emptied of data but still has a key, it should still ++++++ CVE-2026-5766.patch ++++++ >From 2ec27eda3ba6c14f0856e6e3eb1df07c41fd95e6 Mon Sep 17 00:00:00 2001 From: Jacob Walls <[email protected]> Date: Tue, 24 Mar 2026 20:53:27 +0100 Subject: [PATCH] [5.2.x] Fixed CVE-2026-5766 -- Enforced DATA_UPLOAD_MAX_MEMORY_SIZE in MemoryFileUploadHandler on ASGI. In ASGI deployments, Content-Length is not guaranteed to reflect the actual request body size, so relying on it to gate memory allocation allowed the limit to be bypassed. The handler now enforces DATA_UPLOAD_MAX_MEMORY_SIZE regardless of the declared header value. Thanks to Kyle Agronick for the report. Refs #35289. Co-authored-by: Natalia <[email protected]> Backport of 5a89e341bfc77dd67b7fd57b7091b6430558e1f4 from main. --- django/core/files/uploadhandler.py | 21 ++++++++++++++--- docs/releases/5.2.14.txt | 15 ++++++++++++ tests/asgi/tests.py | 32 +++++++++++++++++++++++-- tests/requests_tests/tests.py | 38 ++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 5 deletions(-) Index: Django-4.2.11/django/core/files/uploadhandler.py =================================================================== --- Django-4.2.11.orig/django/core/files/uploadhandler.py +++ Django-4.2.11/django/core/files/uploadhandler.py @@ -2,7 +2,7 @@ Base file upload handler classes, and the built-in concrete subclasses """ import os -from io import BytesIO +from io import BytesIO, UnsupportedOperation from django.conf import settings from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile @@ -201,9 +201,24 @@ class MemoryFileUploadHandler(FileUpload Use the content_length to signal whether or not this handler should be used. """ - # Check the content-length header to see if we should # If the post is too large, we cannot use the Memory handler. - self.activated = content_length <= settings.FILE_UPLOAD_MAX_MEMORY_SIZE + # Content-Length can be absent or understated (for example + # `Transfer-Encoding: chunked` on ASGI), so for seekable streams (such + # as SpooledTemporaryFile on ASGI), check the actual size. + + stream = getattr(input_data, "_stream", input_data) + try: + content_length = stream.seek(0, os.SEEK_END) + except (UnsupportedOperation, AttributeError): + # Cannot seek; fall back to the Content-Length parameter. + # On WSGI the stream enforces this value so it is trustworthy. + pass + else: + stream.seek(0) + self.activated = ( + content_length is not None + and content_length <= settings.FILE_UPLOAD_MAX_MEMORY_SIZE + ) def new_file(self, *args, **kwargs): super().new_file(*args, **kwargs) Index: Django-4.2.11/tests/asgi/tests.py =================================================================== --- Django-4.2.11.orig/tests/asgi/tests.py +++ Django-4.2.11/tests/asgi/tests.py @@ -9,6 +9,7 @@ from asgiref.testing import ApplicationC from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler from django.core.asgi import ASGIHandler, get_asgi_application from django.core.exceptions import RequestDataTooBig +from django.core.files.uploadedfile import InMemoryUploadedFile from django.core.handlers.asgi import ASGIRequest from django.core.signals import request_finished, request_started from django.db import close_old_connections @@ -361,7 +362,7 @@ class ASGITest(SimpleTestCase): sync_waiter.active_threads.clear() -class DataUploadMaxMemorySizeASGITests(SimpleTestCase): +class MaxMemorySizeASGITests(SimpleTestCase): def make_request( self, body, @@ -471,6 +472,34 @@ class DataUploadMaxMemorySizeASGITests(S self.addCleanup(uploaded.close) self.assertEqual(uploaded.read(), file_content) + def test_multipart_file_upload_limited_by_file_upload_max(self): + boundary = "testboundary" + file_content = b"x" * 100 + body = ( + ( + f"--{boundary}\r\n" + f'Content-Disposition: form-data; name="file"; filename="test.txt"\r\n' + f"Content-Type: application/octet-stream\r\n" + f"\r\n" + ).encode() + + file_content + + f"\r\n--{boundary}--\r\n".encode() + ) + # Provide an understated content-length. + request = self.make_request( + body, + content_type=f"multipart/form-data; boundary={boundary}".encode(), + content_length=9, + ) + with self.settings(FILE_UPLOAD_MAX_MEMORY_SIZE=10): + files = request.FILES + self.assertEqual(len(files), 1) + uploaded = files["file"] + # The file is not loaded into memory. + self.assertNotIsInstance(uploaded, InMemoryUploadedFile) + self.addCleanup(uploaded.close) + self.assertEqual(uploaded.read(), file_content) + async def test_read_body_buffers_all_chunks(self): # read_body() consumes all chunks regardless of # DATA_UPLOAD_MAX_MEMORY_SIZE; the limit is enforced later when Index: Django-4.2.11/tests/requests_tests/tests.py =================================================================== --- Django-4.2.11.orig/tests/requests_tests/tests.py +++ Django-4.2.11/tests/requests_tests/tests.py @@ -5,6 +5,7 @@ from unittest import mock from urllib.parse import urlencode from django.core.exceptions import DisallowedHost +from django.core.files.uploadhandler import MemoryFileUploadHandler from django.core.handlers.wsgi import LimitedStream, WSGIRequest from django.http import ( HttpHeaders, @@ -815,6 +816,44 @@ class RequestsTests(SimpleTestCase): self.assertEqual(request_copy.session, {}) +class MemoryFileUploadHandlerTests(SimpleTestCase): + def test_handle_raw_input_wsgi_request_within_limit_activated(self): + + class WSGIRequest: + def __init__(self, body): + self._stream = LimitedStream(BytesIO(body), len(body)) + + handler = MemoryFileUploadHandler() + with self.settings(FILE_UPLOAD_MAX_MEMORY_SIZE=10): + handler.handle_raw_input(WSGIRequest(b"x" * 5), {}, 5, None) + self.assertIs(handler.activated, True) + + def test_handle_raw_input_wsgi_request_exceeds_limit_deactivated(self): + + class WSGIRequest: + def __init__(self, body): + self._stream = LimitedStream(BytesIO(body), len(body)) + + handler = MemoryFileUploadHandler() + with self.settings(FILE_UPLOAD_MAX_MEMORY_SIZE=10): + handler.handle_raw_input(WSGIRequest(b"x" * 15), {}, 15, None) + self.assertIs(handler.activated, False) + + def test_handle_raw_input_seekable_within_limit_activated(self): + handler = MemoryFileUploadHandler() + with self.settings(FILE_UPLOAD_MAX_MEMORY_SIZE=10): + # content_length param is understated (0) but actual size is 10. + handler.handle_raw_input(BytesIO(b"x" * 10), {}, 0, None) + self.assertIs(handler.activated, True) + + def test_handle_raw_input_seekable_exceeds_limit_deactivated(self): + handler = MemoryFileUploadHandler() + with self.settings(FILE_UPLOAD_MAX_MEMORY_SIZE=10): + # content_length param is understated (0) but actual size is 15. + handler.handle_raw_input(BytesIO(b"x" * 15), {}, 0, None) + self.assertIs(handler.activated, False) + + class HostValidationTests(SimpleTestCase): poisoned_hosts = [ "[email protected]", ++++++ CVE-2026-6907.patch ++++++ >From 2115d4eaee15107f5cd290d7cfcc5ffe3ad43661 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <[email protected]> Date: Thu, 23 Apr 2026 14:53:28 +0200 Subject: [PATCH] [5.2.x] 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. Backport of c79bdfc1351ef2a2ad95df36241a74c736ef20a1 from main. --- django/middleware/cache.py | 4 ++++ docs/releases/5.2.14.txt | 10 ++++++++++ tests/cache/tests.py | 25 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/django/middleware/cache.py b/django/middleware/cache.py index df26def6b414..880ceaf7c15a 100644 --- a/django/middleware/cache.py +++ b/django/middleware/cache.py @@ -104,6 +104,10 @@ def process_response(self, request, response): if "private" in response.get("Cache-Control", ()): 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/tests/cache/tests.py b/tests/cache/tests.py index 2636a7d6ce09..306a6ac3ffca 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -2505,6 +2505,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"]) @@ -2733,6 +2745,19 @@ def test_cached_control_private_not_cached(self): response = view_with_private_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
