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

Reply via email to