18 Commits

Author SHA256 Message Date
296190542e Accepting request 1326319 from devel:languages:python:django
OBS-URL: https://build.opensuse.org/request/show/1326319
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-Django?expand=0&rev=143
2026-01-12 09:16:08 +00:00
509b653a93 - Update to 5.2.10
* Fixed a bug in Django 5.2 where data exceeding max_length was
    silently truncated by QuerySet.bulk_create() on PostgreSQL
  * Fixed a bug where management command colorized help (introduced
    in Python 3.14) ignored the --no-color option and the DJANGO_COLORS
    setting
- Drop merged test_strip_tags_incomplete.patch

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:django/python-Django?expand=0&rev=224
2026-01-09 10:19:53 +00:00
5b160c9df9 Accepting request 1324665 from devel:languages:python:django
OBS-URL: https://build.opensuse.org/request/show/1324665
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-Django?expand=0&rev=142
2025-12-29 14:15:47 +00:00
d1f5740358 - Add test_strip_tags_incomplete.patch to fix behaviour with changes
in the Python interpreter
- Rebase test_strip_tags.patch

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:django/python-Django?expand=0&rev=222
2025-12-29 09:49:28 +00:00
a08c10b5c3 Accepting request 1321589 from devel:languages:python:django
OBS-URL: https://build.opensuse.org/request/show/1321589
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-Django?expand=0&rev=141
2025-12-09 11:46:45 +00:00
a1efaf50e9 - Update to 5.2.9 (bsc#1254437)
* CVE-2025-13372: Potential SQL injection in FilteredRelation column
    aliases on PostgreSQL
  * CVE-2025-64460: Potential denial-of-service vulnerability in XML
    Deserializer
  * Fixed a crash on Python 3.14+ that prevented template tag functions
    from being registered
  * Fixed more bugs and regressions, see upstream release notes

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:django/python-Django?expand=0&rev=220
2025-12-08 13:35:41 +00:00
1cb7e345a7 Accepting request 1315976 from devel:languages:python:django
Forwarded request #1315948 from mcalabkova

- Update to 5.2.8 (bsc#1252926)
    * CVE-2025-64459: Potential SQL injection via _connector keyword argument
    * Added compatibility for oracledb 3.4.0
    * Fixed a bug in Django 5.2 where QuerySet.first() and QuerySet.last()
      raised an error on querysets performing aggregation that selected all
      fields of a composite primary key.

OBS-URL: https://build.opensuse.org/request/show/1315976
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-Django?expand=0&rev=140
2025-11-07 17:20:57 +00:00
fdcf2d256d - Update to 5.2.8 (bsc#1252926)
* CVE-2025-64459: Potential SQL injection via _connector keyword argument
  * Added compatibility for oracledb 3.4.0
  * Fixed a bug in Django 5.2 where QuerySet.first() and QuerySet.last()
    raised an error on querysets performing aggregation that selected all
    fields of a composite primary key.

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:django/python-Django?expand=0&rev=218
2025-11-06 09:57:16 +00:00
17b3c221a5 Accepting request 1308583 from devel:languages:python:django
OBS-URL: https://build.opensuse.org/request/show/1308583
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-Django?expand=0&rev=139
2025-10-03 13:42:46 +00:00
be4f1447ca - Update to 5.2.7 (bsc#1250485, bsc#1250487)
* CVE-2025-59681: Potential SQL injection in QuerySet.annotate(), alias(),
    aggregate(), and extra() on MySQL and MariaDB
  * CVE-2025-59682: Potential partial directory-traversal via archive.extract()
  * Fixed a regression in Django 5.2 that reduced the color contrast of the
    label of filter_horizontal and filter_vertical widgets within a TabularInline

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:django/python-Django?expand=0&rev=216
2025-10-02 10:47:09 +00:00
878b120faf Accepting request 1302720 from devel:languages:python:django
OBS-URL: https://build.opensuse.org/request/show/1302720
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-Django?expand=0&rev=138
2025-09-05 19:42:17 +00:00
98f379ab77 - Update to 5.2.6 (bsc#1248810)
* CVE-2025-57833: Potential SQL injection in FilteredRelation column aliases
  * Fixed a bug where using QuerySet.values() or values_list() with a ForeignObject
    composed of multiple fields returned incorrect results instead of tuples of
    the referenced fields
- Rebased test_strip_tags.patch

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:django/python-Django?expand=0&rev=214
2025-09-04 11:14:46 +00:00
c617be0174 Accepting request 1299114 from devel:languages:python:django
OBS-URL: https://build.opensuse.org/request/show/1299114
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-Django?expand=0&rev=137
2025-08-13 14:23:21 +00:00
9d51974b18 - Update to 5.2.5
* Fixed a regression in Django 5.2.1 that prevented the usage of UNNEST
    PostgreSQL strategy of QuerySet.bulk_create() with foreign keys
  * Fixed a crash in Django 5.2 when filtering against a composite primary key
    using a tuple containing expressions
  * Fixed a crash in Django 5.2 when validating a model that uses
    GeneratedField or constraints composed of Q and Case lookups
  * Added compatibility for docutils 0.22
  * Fixed a crash in Django 5.2 when using a ManyToManyField on a model with
    a composite primary key, by extending the fields.E347 system check
- Convert to libalternatives on SLE-16-based and newer systems

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:django/python-Django?expand=0&rev=212
2025-08-12 15:39:39 +00:00
78db34879c Accepting request 1296919 from devel:languages:python:django
- Reinstate Requires on tzdata, a lot of packages use it.

OBS-URL: https://build.opensuse.org/request/show/1296919
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-Django?expand=0&rev=136
2025-08-03 11:36:22 +00:00
b367c16b9f - Reinstate Requires on tzdata, a lot of packages use it.
OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:django/python-Django?expand=0&rev=210
2025-08-01 02:09:29 +00:00
33a429e3af Accepting request 1296418 from devel:languages:python:django
- Add patch support-msgfmt-0.25.patch:
  * Support msgfmt 0.25 error messages changes. (bsc#1246966)
- Remove unneeded Requires on tzdata.

OBS-URL: https://build.opensuse.org/request/show/1296418
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-Django?expand=0&rev=135
2025-07-31 15:44:58 +00:00
c5bef52b95 - Add patch support-msgfmt-0.25.patch:
* Support msgfmt 0.25 error messages changes. (bsc#1246966)
- Remove unneeded Requires on tzdata.

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:django/python-Django?expand=0&rev=208
2025-07-30 06:04:11 +00:00
23 changed files with 299 additions and 1745 deletions

View File

@@ -1,67 +0,0 @@
From 479415ce5249bcdebeb6570c72df2a87f45a7bbf Mon Sep 17 00:00:00 2001
From: Jacob Walls <jacobtylerwalls@gmail.com>
Date: Mon, 17 Nov 2025 17:09:54 -0500
Subject: [PATCH] [5.2.x] Fixed CVE-2025-13372 -- Protected FilteredRelation
against SQL injection in column aliases on PostgreSQL.
Follow-up to CVE-2025-57833.
Thanks Stackered for the report, and Simon Charette and Mariusz Felisiak
for the reviews.
Backport of 5b90ca1e7591fa36fccf2d6dad67cf1477e6293e from main.
---
django/db/backends/postgresql/compiler.py | 11 ++++++++++-
docs/releases/4.2.27.txt | 8 ++++++++
docs/releases/5.1.15.txt | 8 ++++++++
docs/releases/5.2.9.txt | 8 ++++++++
tests/annotations/tests.py | 11 +++++++++++
5 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/django/db/backends/postgresql/compiler.py b/django/db/backends/postgresql/compiler.py
index dc2db148aed5..38b61c489838 100644
--- a/django/db/backends/postgresql/compiler.py
+++ b/django/db/backends/postgresql/compiler.py
@@ -1,6 +1,6 @@
from django.db.models.sql.compiler import (
SQLAggregateCompiler,
- SQLCompiler,
+ SQLCompiler as BaseSQLCompiler,
SQLDeleteCompiler,
)
from django.db.models.sql.compiler import SQLInsertCompiler as BaseSQLInsertCompiler
@@ -25,6 +25,15 @@ def __str__(self):
return "UNNEST(%s)" % ", ".join(self)
+class SQLCompiler(BaseSQLCompiler):
+ def quote_name_unless_alias(self, name):
+ if "$" in name:
+ raise ValueError(
+ "Dollar signs are not permitted in column aliases on PostgreSQL."
+ )
+ return super().quote_name_unless_alias(name)
+
+
class SQLInsertCompiler(BaseSQLInsertCompiler):
def assemble_as_sql(self, fields, value_rows):
# Specialize bulk-insertion of literal values through UNNEST to
diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py
index 7a1212122427..78e5408d0fe7 100644
--- a/tests/annotations/tests.py
+++ b/tests/annotations/tests.py
@@ -1507,3 +1507,14 @@ def test_alias_filtered_relation_sql_injection(self):
)
with self.assertRaisesMessage(ValueError, msg):
Book.objects.alias(**{crafted_alias: FilteredRelation("authors")})
+
+ def test_alias_filtered_relation_sql_injection_dollar_sign(self):
+ qs = Book.objects.alias(
+ **{"crafted_alia$": FilteredRelation("authors")}
+ ).values("name", "crafted_alia$")
+ if connection.vendor == "postgresql":
+ msg = "Dollar signs are not permitted in column aliases on PostgreSQL."
+ with self.assertRaisesMessage(ValueError, msg):
+ list(qs)
+ else:
+ self.assertEqual(qs.first()["name"], self.b1.name)

View File

@@ -1,121 +0,0 @@
From 184e38ab0a061c365f5775676a074796d8abd02f Mon Sep 17 00:00:00 2001
From: Jake Howard <git@theorangeone.net>
Date: Wed, 19 Nov 2025 16:52:28 +0000
Subject: [PATCH] [5.2.x] Fixed CVE-2025-13473 -- Standardized timing of
check_password() in mod_wsgi auth handler.
Refs CVE-2024-39329, #20760.
Thanks Stackered for the report, and Jacob Walls and Markus Holtermann
for the reviews.
Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
Backport of 3eb814e02a4c336866d4189fa0c24fd1875863ed from main.
---
django/contrib/auth/handlers/modwsgi.py | 37 ++++++++++++++++++++-----
docs/releases/4.2.28.txt | 10 +++++++
docs/releases/5.2.11.txt | 10 +++++++
tests/auth_tests/test_handlers.py | 28 +++++++++++++++++++
4 files changed, 78 insertions(+), 7 deletions(-)
diff --git a/django/contrib/auth/handlers/modwsgi.py b/django/contrib/auth/handlers/modwsgi.py
index 591ec72cb4cd..086db89fc846 100644
--- a/django/contrib/auth/handlers/modwsgi.py
+++ b/django/contrib/auth/handlers/modwsgi.py
@@ -4,24 +4,47 @@
UserModel = auth.get_user_model()
+def _get_user(username):
+ """
+ Return the UserModel instance for `username`.
+
+ If no matching user exists, or if the user is inactive, return None, in
+ which case the default password hasher is run to mitigate timing attacks.
+ """
+ try:
+ user = UserModel._default_manager.get_by_natural_key(username)
+ except UserModel.DoesNotExist:
+ user = None
+ else:
+ if not user.is_active:
+ user = None
+
+ if user is None:
+ # Run the default password hasher once to reduce the timing difference
+ # between existing/active and nonexistent/inactive users (#20760).
+ UserModel().set_password("")
+
+ return user
+
+
def check_password(environ, username, password):
"""
Authenticate against Django's auth database.
mod_wsgi docs specify None, True, False as return value depending
on whether the user exists and authenticates.
+
+ Return None if the user does not exist, return False if the user exists but
+ password is not correct, and return True otherwise.
+
"""
# db connection state is managed similarly to the wsgi handler
# as mod_wsgi may call these functions outside of a request/response cycle
db.reset_queries()
try:
- try:
- user = UserModel._default_manager.get_by_natural_key(username)
- except UserModel.DoesNotExist:
- return None
- if not user.is_active:
- return None
- return user.check_password(password)
+ user = _get_user(username)
+ if user:
+ return user.check_password(password)
finally:
db.close_old_connections()
diff --git a/tests/auth_tests/test_handlers.py b/tests/auth_tests/test_handlers.py
index a6b53a9ef11d..32b4371198d4 100644
--- a/tests/auth_tests/test_handlers.py
+++ b/tests/auth_tests/test_handlers.py
@@ -1,4 +1,7 @@
+from unittest import mock
+
from django.contrib.auth.handlers.modwsgi import check_password, groups_for_user
+from django.contrib.auth.hashers import get_hasher
from django.contrib.auth.models import Group, User
from django.test import TransactionTestCase, override_settings
@@ -73,3 +76,28 @@ def test_groups_for_user(self):
self.assertEqual(groups_for_user({}, "test"), [b"test_group"])
self.assertEqual(groups_for_user({}, "test1"), [])
+
+ def test_check_password_fake_runtime(self):
+ """
+ Hasher is run once regardless of whether the user exists. Refs #20760.
+ """
+ User.objects.create_user("test", "test@example.com", "test")
+ User.objects.create_user("inactive", "test@nono.com", "test", is_active=False)
+ User.objects.create_user("unusable", "test@nono.com")
+
+ hasher = get_hasher()
+
+ for username, password in [
+ ("test", "test"),
+ ("test", "wrong"),
+ ("inactive", "test"),
+ ("inactive", "wrong"),
+ ("unusable", "test"),
+ ("doesnotexist", "test"),
+ ]:
+ with (
+ self.subTest(username=username, password=password),
+ mock.patch.object(hasher, "encode") as mock_make_password,
+ ):
+ check_password({}, username, password)
+ mock_make_password.assert_called_once()

View File

@@ -1,95 +0,0 @@
From 1ba90069c12836db46981bdf75b0e661db5849ce Mon Sep 17 00:00:00 2001
From: Jake Howard <git@theorangeone.net>
Date: Wed, 14 Jan 2026 15:25:45 +0000
Subject: [PATCH] [5.2.x] Fixed CVE-2025-14550 -- Optimized repeated header
parsing in ASGI requests.
Thanks Jiyong Yang for the report, and Natalia Bidart, Jacob Walls, and
Shai Berger for reviews.
Backport of eb22e1d6d643360e952609ef562c139a100ea4eb from main.
---
django/core/handlers/asgi.py | 7 ++++---
docs/releases/4.2.28.txt | 12 ++++++++++++
docs/releases/5.2.11.txt | 12 ++++++++++++
tests/asgi/tests.py | 28 +++++++++++++++++++++++++++-
4 files changed, 55 insertions(+), 4 deletions(-)
diff --git a/django/core/handlers/asgi.py b/django/core/handlers/asgi.py
index bb6a6bfb3ce7..2dfcc7f31d3f 100644
--- a/django/core/handlers/asgi.py
+++ b/django/core/handlers/asgi.py
@@ -3,6 +3,7 @@
import sys
import tempfile
import traceback
+from collections import defaultdict
from contextlib import aclosing
from asgiref.sync import ThreadSensitiveContext, sync_to_async
@@ -83,6 +84,7 @@ def __init__(self, scope, body_file):
self.META["SERVER_NAME"] = "unknown"
self.META["SERVER_PORT"] = "0"
# Headers go into META.
+ _headers = defaultdict(list)
for name, value in self.scope.get("headers", []):
name = name.decode("latin1")
if name == "content-length":
@@ -94,9 +96,8 @@ def __init__(self, scope, body_file):
# HTTP/2 say only ASCII chars are allowed in headers, but decode
# latin1 just in case.
value = value.decode("latin1")
- if corrected_name in self.META:
- value = self.META[corrected_name] + "," + value
- self.META[corrected_name] = value
+ _headers[corrected_name].append(value)
+ self.META.update({name: ",".join(value) for name, value in _headers.items()})
# Pull out request encoding, if provided.
self._set_content_type_params(self.META)
# Directly assign the body file to be our stream.
diff --git a/tests/asgi/tests.py b/tests/asgi/tests.py
index 0b1d3cd60879..81a53e539742 100644
--- a/tests/asgi/tests.py
+++ b/tests/asgi/tests.py
@@ -211,7 +211,7 @@ async def test_post_body(self):
self.assertEqual(response_body["type"], "http.response.body")
self.assertEqual(response_body["body"], b"Echo!")
- async def test_create_request_error(self):
+ async def test_request_too_big_request_error(self):
# Track request_finished signal.
signal_handler = SignalHandler()
request_finished.connect(signal_handler)
@@ -242,6 +242,32 @@ class TestASGIHandler(ASGIHandler):
signal_handler.calls[0]["thread"], threading.current_thread()
)
+ async def test_meta_not_modified_with_repeat_headers(self):
+ scope = self.async_request_factory._base_scope(path="/", http_version="2.0")
+ scope["headers"] = [(b"foo", b"bar")] * 200_000
+
+ setitem_count = 0
+
+ class InstrumentedDict(dict):
+ def __setitem__(self, *args, **kwargs):
+ nonlocal setitem_count
+ setitem_count += 1
+ super().__setitem__(*args, **kwargs)
+
+ class InstrumentedASGIRequest(ASGIRequest):
+ @property
+ def META(self):
+ return self._meta
+
+ @META.setter
+ def META(self, value):
+ self._meta = InstrumentedDict(**value)
+
+ request = InstrumentedASGIRequest(scope, None)
+
+ self.assertEqual(len(request.headers["foo"].split(",")), 200_000)
+ self.assertLessEqual(setitem_count, 100)
+
async def test_cancel_post_request_with_sync_processing(self):
"""
The request.body object should be available and readable in view

View File

@@ -1,82 +0,0 @@
From 4c044fcc866ec226f612c475950b690b0139d243 Mon Sep 17 00:00:00 2001
From: Jake Howard <git@theorangeone.net>
Date: Wed, 13 Aug 2025 14:13:42 +0200
Subject: [PATCH] [5.2.x] Fixed CVE-2025-57833 -- Protected FilteredRelation
against SQL injection in column aliases.
Thanks Eyal Gabay (EyalSec) for the report.
Backport of 51711717098d3f469f795dfa6bc3758b24f69ef7 from main.
---
django/db/models/sql/query.py | 1 +
docs/releases/4.2.24.txt | 7 +++++++
docs/releases/5.1.12.txt | 7 +++++++
docs/releases/5.2.6.txt | 7 +++++++
tests/annotations/tests.py | 24 ++++++++++++++++++++++++
5 files changed, 46 insertions(+)
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 9b44d017ffe3..5247616086aa 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -1696,6 +1696,7 @@ def _add_q(
return target_clause, needed_inner
def add_filtered_relation(self, filtered_relation, alias):
+ self.check_alias(alias)
filtered_relation.alias = alias
relation_lookup_parts, relation_field_parts, _ = self.solve_lookup_type(
filtered_relation.relation_name
diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py
index 6c0d7b668c33..060d6324c74c 100644
--- a/tests/annotations/tests.py
+++ b/tests/annotations/tests.py
@@ -14,6 +14,7 @@
Exists,
ExpressionWrapper,
F,
+ FilteredRelation,
FloatField,
Func,
IntegerField,
@@ -1164,6 +1165,15 @@ def test_alias_sql_injection(self):
with self.assertRaisesMessage(ValueError, msg):
Book.objects.annotate(**{crafted_alias: Value(1)})
+ def test_alias_filtered_relation_sql_injection(self):
+ crafted_alias = """injected_name" from "annotations_book"; --"""
+ msg = (
+ "Column aliases cannot contain whitespace characters, quotation marks, "
+ "semicolons, or SQL comments."
+ )
+ with self.assertRaisesMessage(ValueError, msg):
+ Book.objects.annotate(**{crafted_alias: FilteredRelation("author")})
+
def test_alias_forbidden_chars(self):
tests = [
'al"ias',
@@ -1189,6 +1199,11 @@ def test_alias_forbidden_chars(self):
with self.assertRaisesMessage(ValueError, msg):
Book.objects.annotate(**{crafted_alias: Value(1)})
+ with self.assertRaisesMessage(ValueError, msg):
+ Book.objects.annotate(
+ **{crafted_alias: FilteredRelation("authors")}
+ )
+
@skipUnless(connection.vendor == "postgresql", "PostgreSQL tests")
@skipUnlessDBFeature("supports_json_field")
def test_set_returning_functions(self):
@@ -1482,3 +1497,12 @@ def test_alias_sql_injection(self):
)
with self.assertRaisesMessage(ValueError, msg):
Book.objects.alias(**{crafted_alias: Value(1)})
+
+ def test_alias_filtered_relation_sql_injection(self):
+ crafted_alias = """injected_name" from "annotations_book"; --"""
+ msg = (
+ "Column aliases cannot contain whitespace characters, quotation marks, "
+ "semicolons, or SQL comments."
+ )
+ with self.assertRaisesMessage(ValueError, msg):
+ Book.objects.alias(**{crafted_alias: FilteredRelation("authors")})

View File

@@ -1,173 +0,0 @@
From b4d3036c04ae71d611edecf5cfc7d4e5b5927f81 Mon Sep 17 00:00:00 2001
From: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Date: Wed, 10 Sep 2025 09:53:52 +0200
Subject: [PATCH 1/2] [5.2.x] Fixed CVE-2025-59681 -- Protected
QuerySet.annotate(), alias(), aggregate(), and extra() against SQL injection
in column aliases on MySQL/MariaDB.
Thanks sw0rd1ight for the report.
Follow up to 93cae5cb2f9a4ef1514cf1a41f714fef08005200.
---
django/db/models/sql/query.py | 8 ++++----
docs/releases/4.2.25.txt | 9 ++++++++-
docs/releases/5.1.13.txt | 9 ++++++++-
docs/releases/5.2.7.txt | 9 +++++++++
tests/aggregation/tests.py | 4 ++--
tests/annotations/tests.py | 23 ++++++++++++-----------
tests/expressions/test_queryset_values.py | 8 ++++----
tests/queries/tests.py | 4 ++--
8 files changed, 49 insertions(+), 25 deletions(-)
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 5247616086..3a1cd73951 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -48,9 +48,9 @@ from django.utils.tree import Node
__all__ = ["Query", "RawQuery"]
-# Quotation marks ('"`[]), whitespace characters, semicolons, or inline
+# Quotation marks ('"`[]), whitespace characters, semicolons, hashes, or inline
# SQL comments are forbidden in column aliases.
-FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|--|/\*|\*/")
+FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|#|--|/\*|\*/")
# Inspired from
# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
@@ -1208,8 +1208,8 @@ class Query(BaseExpression):
def check_alias(self, alias):
if FORBIDDEN_ALIAS_PATTERN.search(alias):
raise ValueError(
- "Column aliases cannot contain whitespace characters, quotation marks, "
- "semicolons, or SQL comments."
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "quotation marks, semicolons, or SQL comments."
)
def add_annotation(self, annotation, alias, select=True):
diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py
index bf44c4d25f..2e41f19947 100644
--- a/tests/aggregation/tests.py
+++ b/tests/aggregation/tests.py
@@ -2136,8 +2136,8 @@ class AggregateTestCase(TestCase):
def test_alias_sql_injection(self):
crafted_alias = """injected_name" from "aggregation_author"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, quotation marks, "
- "semicolons, or SQL comments."
+ "Column aliases cannot contain whitespace characters, hashes, quotation "
+ "marks, semicolons, or SQL comments."
)
with self.assertRaisesMessage(ValueError, msg):
Author.objects.aggregate(**{crafted_alias: Avg("age")})
diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py
index 060d6324c7..7a12121224 100644
--- a/tests/annotations/tests.py
+++ b/tests/annotations/tests.py
@@ -1159,8 +1159,8 @@ class NonAggregateAnnotationTestCase(TestCase):
def test_alias_sql_injection(self):
crafted_alias = """injected_name" from "annotations_book"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, quotation marks, "
- "semicolons, or SQL comments."
+ "Column aliases cannot contain whitespace characters, hashes, quotation "
+ "marks, semicolons, or SQL comments."
)
with self.assertRaisesMessage(ValueError, msg):
Book.objects.annotate(**{crafted_alias: Value(1)})
@@ -1168,8 +1168,8 @@ class NonAggregateAnnotationTestCase(TestCase):
def test_alias_filtered_relation_sql_injection(self):
crafted_alias = """injected_name" from "annotations_book"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, quotation marks, "
- "semicolons, or SQL comments."
+ "Column aliases cannot contain whitespace characters, hashes, quotation "
+ "marks, semicolons, or SQL comments."
)
with self.assertRaisesMessage(ValueError, msg):
Book.objects.annotate(**{crafted_alias: FilteredRelation("author")})
@@ -1186,13 +1186,14 @@ class NonAggregateAnnotationTestCase(TestCase):
"ali/*as",
"alias*/",
"alias;",
- # [] are used by MSSQL.
+ # [] and # are used by MSSQL.
"alias[",
"alias]",
+ "ali#as",
]
msg = (
- "Column aliases cannot contain whitespace characters, quotation marks, "
- "semicolons, or SQL comments."
+ "Column aliases cannot contain whitespace characters, hashes, quotation "
+ "marks, semicolons, or SQL comments."
)
for crafted_alias in tests:
with self.subTest(crafted_alias):
@@ -1492,8 +1493,8 @@ class AliasTests(TestCase):
def test_alias_sql_injection(self):
crafted_alias = """injected_name" from "annotations_book"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, quotation marks, "
- "semicolons, or SQL comments."
+ "Column aliases cannot contain whitespace characters, hashes, quotation "
+ "marks, semicolons, or SQL comments."
)
with self.assertRaisesMessage(ValueError, msg):
Book.objects.alias(**{crafted_alias: Value(1)})
@@ -1501,8 +1502,8 @@ class AliasTests(TestCase):
def test_alias_filtered_relation_sql_injection(self):
crafted_alias = """injected_name" from "annotations_book"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, quotation marks, "
- "semicolons, or SQL comments."
+ "Column aliases cannot contain whitespace characters, hashes, quotation "
+ "marks, semicolons, or SQL comments."
)
with self.assertRaisesMessage(ValueError, msg):
Book.objects.alias(**{crafted_alias: FilteredRelation("authors")})
diff --git a/tests/expressions/test_queryset_values.py b/tests/expressions/test_queryset_values.py
index 47bd1358de..080ee06183 100644
--- a/tests/expressions/test_queryset_values.py
+++ b/tests/expressions/test_queryset_values.py
@@ -37,8 +37,8 @@ class ValuesExpressionsTests(TestCase):
def test_values_expression_alias_sql_injection(self):
crafted_alias = """injected_name" from "expressions_company"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, quotation marks, "
- "semicolons, or SQL comments."
+ "Column aliases cannot contain whitespace characters, hashes, quotation "
+ "marks, semicolons, or SQL comments."
)
with self.assertRaisesMessage(ValueError, msg):
Company.objects.values(**{crafted_alias: F("ceo__salary")})
@@ -47,8 +47,8 @@ class ValuesExpressionsTests(TestCase):
def test_values_expression_alias_sql_injection_json_field(self):
crafted_alias = """injected_name" from "expressions_company"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, quotation marks, "
- "semicolons, or SQL comments."
+ "Column aliases cannot contain whitespace characters, hashes, quotation "
+ "marks, semicolons, or SQL comments."
)
with self.assertRaisesMessage(ValueError, msg):
JSONFieldModel.objects.values(f"data__{crafted_alias}")
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index 38b0a5ddfa..ffaabf48a0 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -1961,8 +1961,8 @@ class Queries5Tests(TestCase):
def test_extra_select_alias_sql_injection(self):
crafted_alias = """injected_name" from "queries_note"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, quotation marks, "
- "semicolons, or SQL comments."
+ "Column aliases cannot contain whitespace characters, hashes, quotation "
+ "marks, semicolons, or SQL comments."
)
with self.assertRaisesMessage(ValueError, msg):
Note.objects.extra(select={crafted_alias: "1"})
--
2.39.5 (Apple Git-154)

View File

@@ -1,74 +0,0 @@
From 3a7091babcb19f213a25dd5bf8ad90fd63c3cba0 Mon Sep 17 00:00:00 2001
From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
Date: Tue, 16 Sep 2025 17:13:36 +0200
Subject: [PATCH 2/2] [5.2.x] Fixed CVE-2025-59682 -- Fixed potential partial
directory-traversal via archive.extract().
Thanks stackered for the report.
Follow up to 05413afa8c18cdb978fcdf470e09f7a12b234a23.
---
django/utils/archive.py | 6 +++++-
docs/releases/4.2.25.txt | 8 ++++++++
docs/releases/5.1.13.txt | 8 ++++++++
docs/releases/5.2.7.txt | 8 ++++++++
tests/utils_tests/test_archive.py | 22 ++++++++++++++++++++++
5 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/django/utils/archive.py b/django/utils/archive.py
index 56f34c0038..c05fbcdc97 100644
--- a/django/utils/archive.py
+++ b/django/utils/archive.py
@@ -145,7 +145,11 @@ class BaseArchive:
def target_filename(self, to_path, name):
target_path = os.path.abspath(to_path)
filename = os.path.abspath(os.path.join(target_path, name))
- if not filename.startswith(target_path):
+ try:
+ if os.path.commonpath([target_path, filename]) != target_path:
+ raise SuspiciousOperation("Archive contains invalid path: '%s'" % name)
+ except ValueError:
+ # Different drives on Windows raises ValueError.
raise SuspiciousOperation("Archive contains invalid path: '%s'" % name)
return filename
diff --git a/tests/utils_tests/test_archive.py b/tests/utils_tests/test_archive.py
index 89a45bc072..24e60039a5 100644
--- a/tests/utils_tests/test_archive.py
+++ b/tests/utils_tests/test_archive.py
@@ -3,6 +3,7 @@ import stat
import sys
import tempfile
import unittest
+import zipfile
from django.core.exceptions import SuspiciousOperation
from django.test import SimpleTestCase
@@ -94,3 +95,24 @@ class TestArchiveInvalid(SimpleTestCase):
with self.subTest(entry), tempfile.TemporaryDirectory() as tmpdir:
with self.assertRaisesMessage(SuspiciousOperation, msg % invalid_path):
archive.extract(os.path.join(archives_dir, entry), tmpdir)
+
+ def test_extract_function_traversal_startswith(self):
+ with tempfile.TemporaryDirectory() as tmpdir:
+ base = os.path.abspath(tmpdir)
+ tarfile_handle = tempfile.NamedTemporaryFile(suffix=".zip", delete=False)
+ tar_path = tarfile_handle.name
+
+ try:
+ tarfile_handle.close()
+ malicious_member = os.path.join(base + "abc", "evil.txt")
+
+ with zipfile.ZipFile(tar_path, "w") as zf:
+ zf.writestr(malicious_member, "evil\n")
+ zf.writestr("test.txt", "data\n")
+
+ with self.assertRaisesMessage(
+ SuspiciousOperation, "Archive contains invalid path"
+ ):
+ archive.extract(tar_path, base)
+ finally:
+ os.remove(tar_path)
--
2.39.5 (Apple Git-154)

View File

@@ -1,108 +0,0 @@
From 251f22215061abaa5afddd8c8177cf658c8442e8 Mon Sep 17 00:00:00 2001
From: Jacob Walls <jacobtylerwalls@gmail.com>
Date: Wed, 24 Sep 2025 15:54:51 -0400
Subject: [PATCH 2/3] [5.2.x] Fixed CVE-2025-62769 -- Prevented SQL injections
in Q/QuerySet via the _connector kwarg.
Thanks cyberstan for the report, Sarah Boyce, Adam Johnson, Simon
Charette, and Jake Howard for the reviews.
---
django/db/models/query_utils.py | 4 ++++
docs/releases/4.2.26.txt | 7 +++++++
docs/releases/5.1.14.txt | 7 +++++++
docs/releases/5.2.8.txt | 7 +++++++
tests/queries/test_q.py | 5 +++++
5 files changed, 30 insertions(+)
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py
index d219c5fb0e..9d237cdc77 100644
--- a/django/db/models/query_utils.py
+++ b/django/db/models/query_utils.py
@@ -48,8 +48,12 @@ class Q(tree.Node):
XOR = "XOR"
default = AND
conditional = True
+ connectors = (None, AND, OR, XOR)
def __init__(self, *args, _connector=None, _negated=False, **kwargs):
+ if _connector not in self.connectors:
+ connector_reprs = ", ".join(f"{conn!r}" for conn in self.connectors[1:])
+ raise ValueError(f"_connector must be one of {connector_reprs}, or None.")
super().__init__(
children=[*args, *sorted(kwargs.items())],
connector=_connector,
diff --git a/tests/queries/test_q.py b/tests/queries/test_q.py
index 1a62aca061..52200b2ecf 100644
--- a/tests/queries/test_q.py
+++ b/tests/queries/test_q.py
@@ -272,6 +272,11 @@ class QTests(SimpleTestCase):
Q(*items, _connector=connector),
)
+ def test_connector_validation(self):
+ msg = f"_connector must be one of {Q.AND!r}, {Q.OR!r}, {Q.XOR!r}, or None."
+ with self.assertRaisesMessage(ValueError, msg):
+ Q(_connector="evil")
+
def test_referenced_base_fields(self):
# Make sure Q.referenced_base_fields retrieves all base fields from
# both filters and F expressions.
--
2.43.0
From b13864f3024abe7afd316081cdf7e67b3b984987 Mon Sep 17 00:00:00 2001
From: Jacob Walls <jacobtylerwalls@gmail.com>
Date: Wed, 24 Sep 2025 15:56:03 -0400
Subject: [PATCH 3/3] [5.2.x] Refs CVE-2025-62769 -- Avoided propagating
invalid arguments to Q on dictionary expansion.
---
django/db/models/query.py | 5 +++++
tests/queries/tests.py | 8 ++++++++
2 files changed, 13 insertions(+)
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 535d91d767..8ed028d140 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -42,6 +42,8 @@ MAX_GET_RESULTS = 21
# The maximum number of items to display in a QuerySet.__repr__
REPR_OUTPUT_SIZE = 20
+PROHIBITED_FILTER_KWARGS = frozenset(["_connector", "_negated"])
+
class BaseIterable:
def __init__(
@@ -1512,6 +1514,9 @@ class QuerySet(AltersData):
return clone
def _filter_or_exclude_inplace(self, negate, args, kwargs):
+ if invalid_kwargs := PROHIBITED_FILTER_KWARGS.intersection(kwargs):
+ invalid_kwargs_str = ", ".join(f"'{k}'" for k in sorted(invalid_kwargs))
+ raise TypeError(f"The following kwargs are invalid: {invalid_kwargs_str}")
if negate:
self._query.add_q(~Q(*args, **kwargs))
else:
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index ffaabf48a0..c1589669b0 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -4506,6 +4506,14 @@ class TestInvalidValuesRelation(SimpleTestCase):
Annotation.objects.filter(tag__in=[123, "abc"])
+class TestInvalidFilterArguments(TestCase):
+ def test_filter_rejects_invalid_arguments(self):
+ school = School.objects.create()
+ msg = "The following kwargs are invalid: '_connector', '_negated'"
+ with self.assertRaisesMessage(TypeError, msg):
+ School.objects.filter(pk=school.pk, _negated=True, _connector="evil")
+
+
class TestTicket24605(TestCase):
def test_ticket_24605(self):
"""
--
2.43.0

View File

@@ -1,199 +0,0 @@
From 99e7d22f55497278d0bcb2e15e72ef532e62a31d Mon Sep 17 00:00:00 2001
From: Shai Berger <shai@platonix.com>
Date: Sat, 11 Oct 2025 21:42:56 +0300
Subject: [PATCH] [5.2.x] Fixed CVE-2025-64460 -- Corrected quadratic inner
text accumulation in XML serializer.
Previously, `getInnerText()` recursively used `list.extend()` on strings,
which added each character from child nodes as a separate list element.
On deeply nested XML content, this caused the overall deserialization
work to grow quadratically with input size, potentially allowing
disproportionate CPU consumption for crafted XML.
The fix separates collection of inner texts from joining them, so that
each subtree is joined only once, reducing the complexity to linear in
the size of the input. These changes also include a mitigation for a
xml.dom.minidom performance issue.
Thanks Seokchan Yoon (https://ch4n3.kr/) for report.
Co-authored-by: Jacob Walls <jacobtylerwalls@gmail.com>
Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
Backport of 50efb718b31333051bc2dcb06911b8fa1358c98c from main.
---
django/core/serializers/xml_serializer.py | 39 +++++++++++++---
docs/releases/4.2.27.txt | 10 +++++
docs/releases/5.1.15.txt | 10 +++++
docs/releases/5.2.9.txt | 10 +++++
docs/topics/serialization.txt | 2 +
tests/serializers/test_deserialization.py | 54 +++++++++++++++++++++++
6 files changed, 119 insertions(+), 6 deletions(-)
diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py
index 360d5309d853..0fa48acf06e5 100644
--- a/django/core/serializers/xml_serializer.py
+++ b/django/core/serializers/xml_serializer.py
@@ -3,7 +3,8 @@
"""
import json
-from xml.dom import pulldom
+from contextlib import contextmanager
+from xml.dom import minidom, pulldom
from xml.sax import handler
from xml.sax.expatreader import ExpatParser as _ExpatParser
@@ -15,6 +16,25 @@
from django.utils.xmlutils import SimplerXMLGenerator, UnserializableContentError
+@contextmanager
+def fast_cache_clearing():
+ """Workaround for performance issues in minidom document checks.
+
+ Speeds up repeated DOM operations by skipping unnecessary full traversal
+ of the DOM tree.
+ """
+ module_helper_was_lambda = False
+ if original_fn := getattr(minidom, "_in_document", None):
+ module_helper_was_lambda = original_fn.__name__ == "<lambda>"
+ if not module_helper_was_lambda:
+ minidom._in_document = lambda node: bool(node.ownerDocument)
+ try:
+ yield
+ finally:
+ if original_fn and not module_helper_was_lambda:
+ minidom._in_document = original_fn
+
+
class Serializer(base.Serializer):
"""Serialize a QuerySet to XML."""
@@ -210,7 +230,8 @@ def _make_parser(self):
def __next__(self):
for event, node in self.event_stream:
if event == "START_ELEMENT" and node.nodeName == "object":
- self.event_stream.expandNode(node)
+ with fast_cache_clearing():
+ self.event_stream.expandNode(node)
return self._handle_object(node)
raise StopIteration
@@ -394,19 +415,25 @@ def _get_model_from_node(self, node, attr):
def getInnerText(node):
"""Get all the inner text of a DOM node (recursively)."""
+ inner_text_list = getInnerTextList(node)
+ return "".join(inner_text_list)
+
+
+def getInnerTextList(node):
+ """Return a list of the inner texts of a DOM node (recursively)."""
# inspired by https://mail.python.org/pipermail/xml-sig/2005-March/011022.html
- inner_text = []
+ result = []
for child in node.childNodes:
if (
child.nodeType == child.TEXT_NODE
or child.nodeType == child.CDATA_SECTION_NODE
):
- inner_text.append(child.data)
+ result.append(child.data)
elif child.nodeType == child.ELEMENT_NODE:
- inner_text.extend(getInnerText(child))
+ result.extend(getInnerTextList(child))
else:
pass
- return "".join(inner_text)
+ return result
# Below code based on Christian Heimes' defusedxml
diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt
index 1e573e6e1d53..e9523e2ac133 100644
--- a/docs/topics/serialization.txt
+++ b/docs/topics/serialization.txt
@@ -173,6 +173,8 @@ Identifier Information
.. _jsonl: https://jsonlines.org/
.. _PyYAML: https://pyyaml.org/
+.. _serialization-formats-xml:
+
XML
---
diff --git a/tests/serializers/test_deserialization.py b/tests/serializers/test_deserialization.py
index 0bbb46b7ce1c..a718a990385a 100644
--- a/tests/serializers/test_deserialization.py
+++ b/tests/serializers/test_deserialization.py
@@ -1,11 +1,15 @@
import json
+import time
import unittest
from django.core.serializers.base import DeserializationError, DeserializedObject
from django.core.serializers.json import Deserializer as JsonDeserializer
from django.core.serializers.jsonl import Deserializer as JsonlDeserializer
from django.core.serializers.python import Deserializer
+from django.core.serializers.xml_serializer import Deserializer as XMLDeserializer
+from django.db import models
from django.test import SimpleTestCase
+from django.test.utils import garbage_collect
from .models import Author
@@ -133,3 +137,53 @@ def test_yaml_bytes_input(self):
self.assertEqual(first_item.object, self.jane)
self.assertEqual(second_item.object, self.joe)
+
+ def test_crafted_xml_performance(self):
+ """The time to process invalid inputs is not quadratic."""
+
+ def build_crafted_xml(depth, leaf_text_len):
+ nested_open = "<nested>" * depth
+ nested_close = "</nested>" * depth
+ leaf = "x" * leaf_text_len
+ field_content = f"{nested_open}{leaf}{nested_close}"
+ return f"""
+ <django-objects version="1.0">
+ <object model="contenttypes.contenttype" pk="1">
+ <field name="app_label">{field_content}</field>
+ <field name="model">m</field>
+ </object>
+ </django-objects>
+ """
+
+ def deserialize(crafted_xml):
+ iterator = XMLDeserializer(crafted_xml)
+ garbage_collect()
+
+ start_time = time.perf_counter()
+ result = list(iterator)
+ end_time = time.perf_counter()
+
+ self.assertEqual(len(result), 1)
+ self.assertIsInstance(result[0].object, models.Model)
+ return end_time - start_time
+
+ def assertFactor(label, params, factor=2):
+ factors = []
+ prev_time = None
+ for depth, length in params:
+ crafted_xml = build_crafted_xml(depth, length)
+ elapsed = deserialize(crafted_xml)
+ if prev_time is not None:
+ factors.append(elapsed / prev_time)
+ prev_time = elapsed
+
+ with self.subTest(label):
+ # Assert based on the average factor to reduce test flakiness.
+ self.assertLessEqual(sum(factors) / len(factors), factor)
+
+ assertFactor(
+ "varying depth, varying length",
+ [(50, 2000), (100, 4000), (200, 8000), (400, 16000), (800, 32000)],
+ 2,
+ )
+ assertFactor("constant depth, varying length", [(100, 1), (100, 1000)], 2)

View File

@@ -1,106 +0,0 @@
From 17a1d64a58ef24c0c3b78d66d86f5415075f18f0 Mon Sep 17 00:00:00 2001
From: Jacob Walls <jacobtylerwalls@gmail.com>
Date: Mon, 19 Jan 2026 15:42:33 -0500
Subject: [PATCH] [5.2.x] Fixed CVE-2026-1207 -- Prevented SQL injections in
RasterField lookups via band index.
Thanks Tarek Nakkouch for the report, and Simon Charette for the initial
triage and review.
Backport of 81aa5292967cd09319c45fe2c1a525ce7b6684d8 from main.
---
.../gis/db/backends/postgis/operations.py | 6 +++
docs/releases/4.2.28.txt | 12 +++++
docs/releases/5.2.11.txt | 12 +++++
tests/gis_tests/rasterapp/test_rasterfield.py | 47 ++++++++++++++++++-
4 files changed, 76 insertions(+), 1 deletion(-)
diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py
index 7a347c52878a..c39f756c792e 100644
--- a/django/contrib/gis/db/backends/postgis/operations.py
+++ b/django/contrib/gis/db/backends/postgis/operations.py
@@ -51,6 +51,9 @@ def check_raster(self, lookup, template_params):
# Look for band indices and inject them if provided.
if lookup.band_lhs is not None and lhs_is_raster:
+ if not isinstance(lookup.band_lhs, int):
+ name = lookup.band_lhs.__class__.__name__
+ raise TypeError(f"Band index must be an integer, but got {name!r}.")
if not self.func:
raise ValueError(
"Band indices are not allowed for this operator, it works on bbox "
@@ -62,6 +65,9 @@ def check_raster(self, lookup, template_params):
)
if lookup.band_rhs is not None and rhs_is_raster:
+ if not isinstance(lookup.band_rhs, int):
+ name = lookup.band_rhs.__class__.__name__
+ raise TypeError(f"Band index must be an integer, but got {name!r}.")
if not self.func:
raise ValueError(
"Band indices are not allowed for this operator, it works on bbox "
diff --git a/tests/gis_tests/rasterapp/test_rasterfield.py b/tests/gis_tests/rasterapp/test_rasterfield.py
index 3f2ce770a9a6..89c4ec4856a7 100644
--- a/tests/gis_tests/rasterapp/test_rasterfield.py
+++ b/tests/gis_tests/rasterapp/test_rasterfield.py
@@ -2,7 +2,11 @@
from django.contrib.gis.db.models.fields import BaseSpatialField
from django.contrib.gis.db.models.functions import Distance
-from django.contrib.gis.db.models.lookups import DistanceLookupBase, GISLookup
+from django.contrib.gis.db.models.lookups import (
+ DistanceLookupBase,
+ GISLookup,
+ RasterBandTransform,
+)
from django.contrib.gis.gdal import GDALRaster
from django.contrib.gis.geos import GEOSGeometry
from django.contrib.gis.measure import D
@@ -356,6 +360,47 @@ def test_lookup_input_band_not_allowed(self):
with self.assertRaisesMessage(ValueError, msg):
qs.count()
+ def test_lookup_invalid_band_rhs(self):
+ rast = GDALRaster(json.loads(JSON_RASTER))
+ qs = RasterModel.objects.filter(rast__contains=(rast, "evil"))
+ msg = "Band index must be an integer, but got 'str'."
+ with self.assertRaisesMessage(TypeError, msg):
+ qs.count()
+
+ def test_lookup_invalid_band_lhs(self):
+ """
+ Typical left-hand side usage is protected against non-integers, but for
+ defense-in-depth purposes, construct custom lookups that evade the
+ `int()` and `+ 1` checks in the lookups shipped by django.contrib.gis.
+ """
+
+ # Evade the int() call in RasterField.get_transform().
+ class MyRasterBandTransform(RasterBandTransform):
+ band_index = "evil"
+
+ def process_band_indices(self, *args, **kwargs):
+ self.band_lhs = self.lhs.band_index
+ self.band_rhs, *self.rhs_params = self.rhs_params
+
+ # Evade the `+ 1` call in BaseSpatialField.process_band_indices().
+ ContainsLookup = RasterModel._meta.get_field("rast").get_lookup("contains")
+
+ class MyContainsLookup(ContainsLookup):
+ def process_band_indices(self, *args, **kwargs):
+ self.band_lhs = self.lhs.band_index
+ self.band_rhs, *self.rhs_params = self.rhs_params
+
+ RasterField = RasterModel._meta.get_field("rast")
+ RasterField.register_lookup(MyContainsLookup, "contains")
+ self.addCleanup(RasterField.register_lookup, ContainsLookup, "contains")
+
+ qs = RasterModel.objects.annotate(
+ transformed=MyRasterBandTransform("rast")
+ ).filter(transformed__contains=(F("transformed"), 1))
+ msg = "Band index must be an integer, but got 'str'."
+ with self.assertRaisesMessage(TypeError, msg):
+ list(qs)
+
def test_isvalid_lookup_with_raster_error(self):
qs = RasterModel.objects.filter(rast__isvalid=True)
msg = (

View File

@@ -1,69 +0,0 @@
From 9f2ada875bbee62ac46032e38ddb22755d67ae5a Mon Sep 17 00:00:00 2001
From: Natalia <124304+nessita@users.noreply.github.com>
Date: Wed, 21 Jan 2026 09:53:10 -0300
Subject: [PATCH] [5.2.x] Fixed CVE-2026-1285 -- Mitigated potential DoS in
django.utils.text.Truncator for HTML input.
The `TruncateHTMLParser` used `deque.remove()` to remove tags from the
stack when processing end tags. With crafted input containing many
unmatched end tags, this caused repeated full scans of the tag stack,
leading to quadratic time complexity.
The fix uses LIFO semantics, only removing a tag from the stack when it
matches the most recently opened tag. This avoids linear scans for
unmatched end tags and reduces complexity to linear time.
Refs #30686 and 6ee37ada3241ed263d8d1c2901b030d964cbd161.
Thanks Seokchan Yoon for the report, and Jake Howard and Jacob Walls for
reviews.
Backport of a33540b3e20b5d759aa8b2e4b9ca0e8edd285344 from main.
---
django/utils/text.py | 9 +++++----
docs/releases/4.2.28.txt | 12 ++++++++++++
docs/releases/5.2.11.txt | 12 ++++++++++++
tests/utils_tests/test_text.py | 10 ++++++++++
4 files changed, 39 insertions(+), 4 deletions(-)
diff --git a/django/utils/text.py b/django/utils/text.py
index 26edde99e336..21efb00b98e8 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -126,10 +126,11 @@ def handle_starttag(self, tag, attrs):
def handle_endtag(self, tag):
if tag not in self.void_elements:
self.output += f"</{tag}>"
- try:
- self.tags.remove(tag)
- except ValueError:
- pass
+ # Remove from the stack only if the tag matches the most recently
+ # opened tag (LIFO). This avoids O(n) linear scans for unmatched
+ # end tags if `deque.remove()` would be called.
+ if self.tags and self.tags[0] == tag:
+ self.tags.popleft()
def handle_data(self, data):
data, output = self.process(data)
diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py
index 63c7889cbcec..11c01874cb5d 100644
--- a/tests/utils_tests/test_text.py
+++ b/tests/utils_tests/test_text.py
@@ -202,6 +202,16 @@ def test_truncate_chars_html_with_html_entities(self):
truncator = text.Truncator("<p>I &lt;3 python, what about you?</p>")
self.assertEqual("<p>I &lt;3 python, wh…</p>", truncator.chars(16, html=True))
+ def test_truncate_chars_html_with_misnested_tags(self):
+ # LIFO removal keeps all tags when a middle tag is closed out of order.
+ # With <a><b><c></b>, the </b> doesn't match <c>, so all tags remain
+ # in the stack and are properly closed at truncation.
+ truncator = text.Truncator("<a><b><c></b>XXXX")
+ self.assertEqual(
+ truncator.chars(2, html=True, truncate=""),
+ "<a><b><c></b>XX</c></b></a>",
+ )
+
def test_truncate_words(self):
truncator = text.Truncator("The quick brown fox jumped over the lazy dog.")
self.assertEqual(

View File

@@ -1,301 +0,0 @@
From 3e68ccdc11c127758745ddf0b4954990b14892bc Mon Sep 17 00:00:00 2001
From: Jake Howard <git@theorangeone.net>
Date: Wed, 21 Jan 2026 11:14:48 +0000
Subject: [PATCH] [5.2.x] Fixed CVE-2026-1287 -- Protected against SQL
injection in column aliases via control characters.
Control characters in FilteredRelation column aliases could be used for
SQL injection attacks. This affected QuerySet.annotate(), aggregate(),
extra(), values(), values_list(), and alias() when using dictionary
expansion with **kwargs.
Thanks Solomon Kebede for the report, and Simon Charette, Jacob Walls,
and Natalia Bidart for reviews.
Backport of e891a84c7ef9962bfcc3b4685690219542f86a22 from main.
---
django/db/models/sql/query.py | 10 +--
docs/releases/4.2.28.txt | 13 ++++
docs/releases/5.2.11.txt | 13 ++++
tests/aggregation/tests.py | 18 ++++--
tests/annotations/tests.py | 74 +++++++++++++++--------
tests/expressions/test_queryset_values.py | 36 +++++++----
tests/queries/tests.py | 18 ++++--
7 files changed, 128 insertions(+), 54 deletions(-)
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 3a1cd739511e..baeac3a05be2 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -48,9 +48,11 @@
__all__ = ["Query", "RawQuery"]
-# Quotation marks ('"`[]), whitespace characters, semicolons, hashes, or inline
-# SQL comments are forbidden in column aliases.
-FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|#|--|/\*|\*/")
+# Quotation marks ('"`[]), whitespace characters, control characters,
+# semicolons, hashes, or inline SQL comments are forbidden in column aliases.
+FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(
+ r"['`\"\]\[;\s\x00-\x1F\x7F-\x9F]|#|--|/\*|\*/"
+)
# Inspired from
# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
@@ -1209,7 +1211,7 @@ def check_alias(self, alias):
if FORBIDDEN_ALIAS_PATTERN.search(alias):
raise ValueError(
"Column aliases cannot contain whitespace characters, hashes, "
- "quotation marks, semicolons, or SQL comments."
+ "control characters, quotation marks, semicolons, or SQL comments."
)
def add_annotation(self, annotation, alias, select=True):
diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py
index 2e41f1994723..f621c53e918a 100644
--- a/tests/aggregation/tests.py
+++ b/tests/aggregation/tests.py
@@ -2,6 +2,7 @@
import math
import re
from decimal import Decimal
+from itertools import chain
from django.core.exceptions import FieldError
from django.db import connection
@@ -2134,13 +2135,18 @@ def test_exists_none_with_aggregate(self):
self.assertEqual(len(qs), 6)
def test_alias_sql_injection(self):
- crafted_alias = """injected_name" from "aggregation_author"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, hashes, quotation "
- "marks, semicolons, or SQL comments."
- )
- with self.assertRaisesMessage(ValueError, msg):
- Author.objects.aggregate(**{crafted_alias: Avg("age")})
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "control characters, quotation marks, semicolons, or SQL comments."
+ )
+ for crafted_alias in [
+ """injected_name" from "aggregation_author"; --""",
+ # Control characters.
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+ ]:
+ with self.subTest(crafted_alias):
+ with self.assertRaisesMessage(ValueError, msg):
+ Author.objects.aggregate(**{crafted_alias: Avg("age")})
def test_exists_extra_where_with_aggregate(self):
qs = Book.objects.annotate(
diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py
index 78e5408d0fe7..0430c68f9175 100644
--- a/tests/annotations/tests.py
+++ b/tests/annotations/tests.py
@@ -1,5 +1,6 @@
import datetime
from decimal import Decimal
+from itertools import chain
from unittest import skipUnless
from django.core.exceptions import FieldDoesNotExist, FieldError
@@ -1157,22 +1158,32 @@ def test_annotation_aggregate_with_m2o(self):
)
def test_alias_sql_injection(self):
- crafted_alias = """injected_name" from "annotations_book"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, hashes, quotation "
- "marks, semicolons, or SQL comments."
- )
- with self.assertRaisesMessage(ValueError, msg):
- Book.objects.annotate(**{crafted_alias: Value(1)})
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "control characters, quotation marks, semicolons, or SQL comments."
+ )
+ for crafted_alias in [
+ """injected_name" from "annotations_book"; --""",
+ # Control characters.
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+ ]:
+ with self.subTest(crafted_alias):
+ with self.assertRaisesMessage(ValueError, msg):
+ Book.objects.annotate(**{crafted_alias: Value(1)})
def test_alias_filtered_relation_sql_injection(self):
- crafted_alias = """injected_name" from "annotations_book"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, hashes, quotation "
- "marks, semicolons, or SQL comments."
- )
- with self.assertRaisesMessage(ValueError, msg):
- Book.objects.annotate(**{crafted_alias: FilteredRelation("author")})
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "control characters, quotation marks, semicolons, or SQL comments."
+ )
+ for crafted_alias in [
+ """injected_name" from "annotations_book"; --""",
+ # Control characters.
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+ ]:
+ with self.subTest(crafted_alias):
+ with self.assertRaisesMessage(ValueError, msg):
+ Book.objects.annotate(**{crafted_alias: FilteredRelation("author")})
def test_alias_forbidden_chars(self):
tests = [
@@ -1190,10 +1201,11 @@ def test_alias_forbidden_chars(self):
"alias[",
"alias]",
"ali#as",
+ "ali\0as",
]
msg = (
- "Column aliases cannot contain whitespace characters, hashes, quotation "
- "marks, semicolons, or SQL comments."
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "control characters, quotation marks, semicolons, or SQL comments."
)
for crafted_alias in tests:
with self.subTest(crafted_alias):
@@ -1491,22 +1503,32 @@ def test_alias_after_values(self):
self.assertEqual(qs.get(pk=self.b1.pk), (self.b1.pk,))
def test_alias_sql_injection(self):
- crafted_alias = """injected_name" from "annotations_book"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, hashes, quotation "
- "marks, semicolons, or SQL comments."
- )
- with self.assertRaisesMessage(ValueError, msg):
- Book.objects.alias(**{crafted_alias: Value(1)})
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "control characters, quotation marks, semicolons, or SQL comments."
+ )
+ for crafted_alias in [
+ """injected_name" from "annotations_book"; --""",
+ # Control characters.
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+ ]:
+ with self.subTest(crafted_alias):
+ with self.assertRaisesMessage(ValueError, msg):
+ Book.objects.alias(**{crafted_alias: Value(1)})
def test_alias_filtered_relation_sql_injection(self):
- crafted_alias = """injected_name" from "annotations_book"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, hashes, quotation "
- "marks, semicolons, or SQL comments."
- )
- with self.assertRaisesMessage(ValueError, msg):
- Book.objects.alias(**{crafted_alias: FilteredRelation("authors")})
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "control characters, quotation marks, semicolons, or SQL comments."
+ )
+ for crafted_alias in [
+ """injected_name" from "annotations_book"; --""",
+ # Control characters.
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+ ]:
+ with self.subTest(crafted_alias):
+ with self.assertRaisesMessage(ValueError, msg):
+ Book.objects.alias(**{crafted_alias: FilteredRelation("authors")})
def test_alias_filtered_relation_sql_injection_dollar_sign(self):
qs = Book.objects.alias(
diff --git a/tests/expressions/test_queryset_values.py b/tests/expressions/test_queryset_values.py
index 080ee06183dc..afd8a51159d5 100644
--- a/tests/expressions/test_queryset_values.py
+++ b/tests/expressions/test_queryset_values.py
@@ -1,3 +1,5 @@
+from itertools import chain
+
from django.db.models import F, Sum
from django.test import TestCase, skipUnlessDBFeature
@@ -35,26 +37,36 @@ def test_values_expression(self):
)
def test_values_expression_alias_sql_injection(self):
- crafted_alias = """injected_name" from "expressions_company"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, hashes, quotation "
- "marks, semicolons, or SQL comments."
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "control characters, quotation marks, semicolons, or SQL comments."
)
- with self.assertRaisesMessage(ValueError, msg):
- Company.objects.values(**{crafted_alias: F("ceo__salary")})
+ for crafted_alias in [
+ """injected_name" from "expressions_company"; --""",
+ # Control characters.
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+ ]:
+ with self.subTest(crafted_alias):
+ with self.assertRaisesMessage(ValueError, msg):
+ Company.objects.values(**{crafted_alias: F("ceo__salary")})
@skipUnlessDBFeature("supports_json_field")
def test_values_expression_alias_sql_injection_json_field(self):
- crafted_alias = """injected_name" from "expressions_company"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, hashes, quotation "
- "marks, semicolons, or SQL comments."
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "control characters, quotation marks, semicolons, or SQL comments."
)
- with self.assertRaisesMessage(ValueError, msg):
- JSONFieldModel.objects.values(f"data__{crafted_alias}")
+ for crafted_alias in [
+ """injected_name" from "expressions_company"; --""",
+ # Control characters.
+ *(chr(c) for c in chain(range(32), range(0x7F, 0xA0))),
+ ]:
+ with self.subTest(crafted_alias):
+ with self.assertRaisesMessage(ValueError, msg):
+ JSONFieldModel.objects.values(f"data__{crafted_alias}")
- with self.assertRaisesMessage(ValueError, msg):
- JSONFieldModel.objects.values_list(f"data__{crafted_alias}")
+ with self.assertRaisesMessage(ValueError, msg):
+ JSONFieldModel.objects.values_list(f"data__{crafted_alias}")
def test_values_expression_group_by(self):
# values() applies annotate() first, so values selected are grouped by
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index c1589669b073..69d728b1cd36 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -2,6 +2,7 @@
import pickle
import sys
import unittest
+from itertools import chain
from operator import attrgetter
from django.core.exceptions import EmptyResultSet, FieldError, FullResultSet
@@ -1959,13 +1960,18 @@ def test_extra_select_literal_percent_s(self):
)
def test_extra_select_alias_sql_injection(self):
- crafted_alias = """injected_name" from "queries_note"; --"""
msg = (
- "Column aliases cannot contain whitespace characters, hashes, quotation "
- "marks, semicolons, or SQL comments."
- )
- with self.assertRaisesMessage(ValueError, msg):
- Note.objects.extra(select={crafted_alias: "1"})
+ "Column aliases cannot contain whitespace characters, hashes, "
+ "control characters, quotation marks, semicolons, or SQL comments."
+ )
+ for crafted_alias in [
+ """injected_name" from "queries_note"; --""",
+ # Control characters.
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
+ ]:
+ with self.subTest(crafted_alias):
+ with self.assertRaisesMessage(ValueError, msg):
+ Note.objects.extra(select={crafted_alias: "1"})
def test_queryset_reuse(self):
# Using querysets doesn't mutate aliases.

View File

@@ -1,93 +0,0 @@
From ab0ad8d39555292b55123adeac57ed64c776f8d9 Mon Sep 17 00:00:00 2001
From: Jacob Walls <jacobtylerwalls@gmail.com>
Date: Wed, 21 Jan 2026 18:00:13 -0500
Subject: [PATCH] [5.2.x] Refs CVE-2026-1312 -- Raised ValueError when
FilteredRelation aliases contain periods.
This prevents failures at the database layer, given that aliases in the
ON clause are not quoted.
Systematically quoting aliases even in FilteredRelation is tracked in
https://code.djangoproject.com/ticket/36795.
Backport of 005d60d97c4dfb117503bdb6f2facfcaf9315d84 from main.
---
django/db/models/sql/query.py | 5 +++++
tests/filtered_relation/tests.py | 13 +++++++++++++
tests/ordering/tests.py | 11 ++++++++---
3 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index baeac3a05be2..324e605b8eea 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -1698,6 +1698,11 @@ def _add_q(
return target_clause, needed_inner
def add_filtered_relation(self, filtered_relation, alias):
+ if "." in alias:
+ raise ValueError(
+ "FilteredRelation doesn't support aliases with periods "
+ "(got %r)." % alias
+ )
self.check_alias(alias)
filtered_relation.alias = alias
relation_lookup_parts, relation_field_parts, _ = self.solve_lookup_type(
diff --git a/tests/filtered_relation/tests.py b/tests/filtered_relation/tests.py
index a9c6e00567cf..a4561d59f95f 100644
--- a/tests/filtered_relation/tests.py
+++ b/tests/filtered_relation/tests.py
@@ -216,6 +216,19 @@ def test_internal_queryset_alias_mapping(self):
str(queryset.query),
)
+ def test_period_forbidden(self):
+ msg = (
+ "FilteredRelation doesn't support aliases with periods (got 'book.alice')."
+ )
+ with self.assertRaisesMessage(ValueError, msg):
+ Author.objects.annotate(
+ **{
+ "book.alice": FilteredRelation(
+ "book", condition=Q(book__title__iexact="poem by alice")
+ )
+ }
+ )
+
def test_multiple(self):
qs = (
Author.objects.annotate(
diff --git a/tests/ordering/tests.py b/tests/ordering/tests.py
index 530a27920e9d..30da025875bd 100644
--- a/tests/ordering/tests.py
+++ b/tests/ordering/tests.py
@@ -15,7 +15,6 @@
Value,
)
from django.db.models.functions import Length, Upper
-from django.db.utils import DatabaseError
from django.test import TestCase
from .models import (
@@ -408,13 +407,19 @@ def test_alias_with_period_shadows_table_name(self):
self.assertNotEqual(qs[0].headline, "Backdated")
relation = FilteredRelation("author")
- qs2 = Article.objects.annotate(**{crafted: relation}).order_by(crafted)
- with self.assertRaises(DatabaseError):
+ msg = (
+ "FilteredRelation doesn't support aliases with periods "
+ "(got 'ordering_article.pub_date')."
+ )
+ with self.assertRaisesMessage(ValueError, msg):
+ qs2 = Article.objects.annotate(**{crafted: relation}).order_by(crafted)
# Before, unlike F(), which causes ordering expressions to be
# replaced by ordinals like n in ORDER BY n, these were ordered by
# pub_date instead of author.
# The Article model orders by -pk, so sorting on author will place
# first any article by author2 instead of the backdated one.
+ # This assertion is reachable if FilteredRelation.__init__() starts
+ # supporting periods in aliases in the future.
self.assertNotEqual(qs2[0].headline, "Backdated")
def test_order_by_pk(self):

View File

@@ -1,92 +0,0 @@
From e863ee273c6553e9b6fa4960a17acb535851857b Mon Sep 17 00:00:00 2001
From: Jacob Walls <jacobtylerwalls@gmail.com>
Date: Wed, 21 Jan 2026 17:53:52 -0500
Subject: [PATCH] [5.2.x] Fixed CVE-2026-1312 -- Protected order_by() from SQL
injection via aliases with periods.
Before, `order_by()` treated a period in a field name as a sign that it
was requested via `.extra(order_by=...)` and thus should be passed
through as raw table and column names, even if `extra()` was not used.
Since periods are permitted in aliases, this meant user-controlled
aliases could force the `order_by()` clause to resolve to a raw table
and column pair instead of the actual target field for the alias.
In practice, only `FilteredRelation` was affected, as the other
expressions we tested, e.g. `F`, aggressively optimize away the ordering
expressions into ordinal positions, e.g. ORDER BY 2, instead of ORDER BY
"table".column.
Thanks Solomon Kebede for the report, and Simon Charette and Jake Howard
for reviews.
Backport of 69065ca869b0970dff8fdd8fafb390bf8b3bf222 from main.
---
django/db/models/sql/compiler.py | 2 +-
docs/releases/4.2.28.txt | 10 ++++++++++
docs/releases/5.2.11.txt | 10 ++++++++++
tests/ordering/tests.py | 25 +++++++++++++++++++++++++
4 files changed, 46 insertions(+), 1 deletion(-)
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 4292243c71ee..7dbdec363568 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -433,7 +433,7 @@ def _order_by_pairs(self):
yield OrderBy(expr, descending=descending), False
continue
- if "." in field:
+ if "." in field and field in self.query.extra_order_by:
# This came in through an extra(order_by=...) addition. Pass it
# on verbatim.
table, col = col.split(".", 1)
diff --git a/tests/ordering/tests.py b/tests/ordering/tests.py
index b29404ed77da..530a27920e9d 100644
--- a/tests/ordering/tests.py
+++ b/tests/ordering/tests.py
@@ -7,6 +7,7 @@
Count,
DateTimeField,
F,
+ FilteredRelation,
Max,
OrderBy,
OuterRef,
@@ -14,6 +15,7 @@
Value,
)
from django.db.models.functions import Length, Upper
+from django.db.utils import DatabaseError
from django.test import TestCase
from .models import (
@@ -392,6 +394,29 @@ def test_extra_ordering_with_table_name(self):
attrgetter("headline"),
)
+ def test_alias_with_period_shadows_table_name(self):
+ """
+ Aliases with periods are not confused for table names from extra().
+ """
+ Article.objects.update(author=self.author_2)
+ Article.objects.create(
+ headline="Backdated", pub_date=datetime(1900, 1, 1), author=self.author_1
+ )
+ crafted = "ordering_article.pub_date"
+
+ qs = Article.objects.annotate(**{crafted: F("author")}).order_by("-" + crafted)
+ self.assertNotEqual(qs[0].headline, "Backdated")
+
+ relation = FilteredRelation("author")
+ qs2 = Article.objects.annotate(**{crafted: relation}).order_by(crafted)
+ with self.assertRaises(DatabaseError):
+ # Before, unlike F(), which causes ordering expressions to be
+ # replaced by ordinals like n in ORDER BY n, these were ordered by
+ # pub_date instead of author.
+ # The Article model orders by -pk, so sorting on author will place
+ # first any article by author2 instead of the backdated one.
+ self.assertNotEqual(qs2[0].headline, "Backdated")
+
def test_order_by_pk(self):
"""
'pk' works as an ordering option in Meta.

View File

@@ -0,0 +1,67 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
This file contains MD5, SHA1, and SHA256 checksums for the
source-code tarball and wheel files of Django 5.2.10, released January 6, 2026.
To use this file, you will need a working install of PGP or other
compatible public-key encryption software. You will also need to have
the Django release manager's public key in your keyring. This key has
the ID ``131403F4D16D8DC7`` and can be imported from the MIT
keyserver, for example, if using the open-source GNU Privacy Guard
implementation of PGP:
gpg --keyserver pgp.mit.edu --recv-key 131403F4D16D8DC7
or via the GitHub API:
curl https://github.com/jacobtylerwalls.gpg | gpg --import -
Once the key is imported, verify this file:
gpg --verify Django-5.2.10.checksum.txt
Once you have verified this file, you can use normal MD5, SHA1, or SHA256
checksumming applications to generate the checksums of the Django
package and compare them to the checksums listed below.
Release packages
================
https://www.djangoproject.com/download/5.2.10/tarball/
https://www.djangoproject.com/download/5.2.10/wheel/
MD5 checksums
=============
98e667c17123f7bbd2d7d1db32f9ccdd django-5.2.10.tar.gz
18c82b021ddfeae5703023a784e46945 django-5.2.10-py3-none-any.whl
SHA1 checksums
==============
a215351eb827f0f8dbab6a83db334dfd45a040d8 django-5.2.10.tar.gz
363f7332c354a91cfd40cbcd4a1df4291064fc64 django-5.2.10-py3-none-any.whl
SHA256 checksums
================
74df100784c288c50a2b5cad59631d71214f40f72051d5af3fdf220c20bdbbbe django-5.2.10.tar.gz
cf85067a64250c95d5f9067b056c5eaa80591929f7e16fbcd997746e40d6c45c django-5.2.10-py3-none-any.whl
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCAAdFiEEU9RpQuAGoqPu3IvIExQD9NFtjccFAmldU1gACgkQExQD9NFt
jccJlA//cN08kKwniqRtHV7cxkqugTTP11AiZirz/QDjWnzmyTT9cUEv7sbHF3An
uJQyVaqHuOITbi437vuLyBR4Z+tN1XA3AGe+TdXeHWmmHhDS2xMzuDUuBzrACfVp
+H1pFSCVtp0koLJHUOtOHmxAsjsBirCJDawY8x2t5cpAiHH1bUn/NkpcNbSKJnUV
FhdbygZnV9ZLbnhsyu++Ym7XT3QdoL4V2GsYsqru3hlwei3jexVZx9RjIUC/krqs
hgC3NyOs0KvHyehjAKZdvcIjFa38mpMxmpXdWpMMPyoN0p6SdBQ8cE+oa83jsrzK
YeHQgGXhbB3ALJ4mIwJirIyyMmEQdTx/Zeh++IayGUUzuoNWi88b3IGCNOOhGRPp
tr0EqM4YKr1pgmNibr5UGwHk1bW3ugT2nUEIs2XnQUb3ItQMEyeh2scuL2IpSkVW
EWJb7CT3WomxSc8xWRm39/g9voXBhQt3dtUBv/VL+MdBBqsK5fL14A0tKd8HagBJ
jo1xVnO6730CIsM61ydHhi5ZGCoXADBS1IFPYPGeaTN3xsMsRPjEkf8KFI/idmkE
HnvuZq3Z0h6ipTR+zdzzNhpP+6VOKBaxgDeU64DGYlSEMvdFJE8HQhmzz9Pks0va
k6qCiXOskgozRGFuiFj2KUQ+a4Qj3UwSOgg6Z5ztbU2aTytgG38=
=GJeZ
-----END PGP SIGNATURE-----

View File

@@ -1,68 +0,0 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
This file contains MD5, SHA1, and SHA256 checksums for the
source-code tarball and wheel files of Django 5.2.4, released July 2, 2025.
To use this file, you will need a working install of PGP or other
compatible public-key encryption software. You will also need to have
the Django release manager's public key in your keyring. This key has
the ID ``2EE82A8D9470983E`` and can be imported from the MIT
keyserver, for example, if using the open-source GNU Privacy Guard
implementation of PGP:
gpg --keyserver pgp.mit.edu --recv-key 2EE82A8D9470983E
or via the GitHub API:
curl https://github.com/nessita.gpg | gpg --import -
Once the key is imported, verify this file:
gpg --verify Django-5.2.4.checksum.txt
Once you have verified this file, you can use normal MD5, SHA1, or SHA256
checksumming applications to generate the checksums of the Django
package and compare them to the checksums listed below.
Release packages
================
https://www.djangoproject.com/download/5.2.4/tarball/
https://www.djangoproject.com/download/5.2.4/wheel/
MD5 checksums
=============
6ecc4875e8cdc08706faea1cc4740fdf django-5.2.4.tar.gz
fee657f7686462d388f274c5f92b634a django-5.2.4-py3-none-any.whl
SHA1 checksums
==============
de45d44e1bb2ceb1c08b8fd0846de920874f71a1 django-5.2.4.tar.gz
a6a7904e3749a0e8937a50643293889929b4b6f7 django-5.2.4-py3-none-any.whl
SHA256 checksums
================
a1228c384f8fa13eebc015196db7b3e08722c5058d4758d20cb287503a540d8f django-5.2.4.tar.gz
60c35bd96201b10c6e7a78121bd0da51084733efa303cc19ead021ab179cef5e django-5.2.4-py3-none-any.whl
-----BEGIN PGP SIGNATURE-----
iQJcBAEBCABGFiEEW1sboQ2FrHxcduOPLugqjZRwmD4FAmhlfcIoHDEyNDMwNCtu
ZXNzaXRhQHVzZXJzLm5vcmVwbHkuZ2l0aHViLmNvbQAKCRAu6CqNlHCYPj5DD/94
KOuOZ5JHtZWknqi1JeV1akzB/RpY7lhL9SbJbVXhdAxOY9Cn4eUG7NsPWa9JnhX1
F/2geBE5mjOZen4ARtGHWxa5vqidqUbscrU9AkqPLn6aecEKi2jXXNkYmmWw/37K
wb92BQtuWkaXyiZ4E6Sledx9yFhcqMDFg27CdNYfAqUWofI6zzSmLIzOlOSVR9Sc
uDrfRqQ4GXlRGT5pIkcIxE0ZKToUYrKgn99PZOmBcLfJgQ4VBt62J6SzZAhhElb3
DUMcVhG2XNIhg7v7DwlVodowDYQdRi2H/ahAa7/m1+uugRbysoGSLLwP+50tDjlj
07zxoJsrL5R9zaMp4pcXQN4bUy3rDz94DkjlXO51f8LwDdStvk4VOYan1W5S9BhP
R0conCFfcg4+iK0pV5e/GeeTwBRHQw8p5RuWfrEpKFi/XQtT0u01hqUGppeuZ9wI
f+Ud9RA8Nrw0ouli4WvfH0RVFuMgUFqScwO88oatuUH5CDPjlV+5usNb7FrmZXv6
AWRopONOcYGF07+FYh0nsoE8enWyxE+JWTJzxT5PGZ3buUO0hlnJ+auoJv8yOVii
ELCSUyi93glWonCBrS41XrNO6+6K/8V9V6iv9/PdGwF1GszbX5Rx4e2lDMA7crYh
1qKGaV3+iAO+Y+vXt6VTy6h5GLg9hun+RQ8TU3Guyg==
=d9C5
-----END PGP SIGNATURE-----

3
django-5.2.10.tar.gz Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:74df100784c288c50a2b5cad59631d71214f40f72051d5af3fdf220c20bdbbbe
size 10880754

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a1228c384f8fa13eebc015196db7b3e08722c5058d4758d20cb287503a540d8f
size 10831909

View File

@@ -1,41 +1,89 @@
-------------------------------------------------------------------
Wed Feb 4 13:21:44 UTC 2026 - Markéta Machová <mmachova@suse.com>
Fri Jan 9 09:41:44 UTC 2026 - Markéta Machová <mmachova@suse.com>
- Update to 5.2.10
* Fixed a bug in Django 5.2 where data exceeding max_length was
silently truncated by QuerySet.bulk_create() on PostgreSQL
* Fixed a bug where management command colorized help (introduced
in Python 3.14) ignored the --no-color option and the DJANGO_COLORS
setting
- Drop merged test_strip_tags_incomplete.patch
-------------------------------------------------------------------
Mon Dec 29 09:10:21 UTC 2025 - Markéta Machová <mmachova@suse.com>
- Add test_strip_tags_incomplete.patch to fix behaviour with changes
in the Python interpreter
- Rebase test_strip_tags.patch
-------------------------------------------------------------------
Wed Feb 4 11:00:19 UTC 2026 - Markéta Machová <mmachova@suse.com>
Mon Dec 8 11:54:46 UTC 2025 - Markéta Machová <mmachova@suse.com>
- Add security patches:
* CVE-2025-14550.patch (bsc#1257403)
* CVE-2026-1312.patch (bsc#1257408)
* CVE-2026-1312-followup.patch (bsc#1257408)
* CVE-2026-1287.patch (bsc#1257407)
* CVE-2026-1207.patch (bsc#1257405)
* CVE-2025-13473.patch (bsc#1257401)
* CVE-2026-1285.patch (bsc#1257406)
- Update to 5.2.9 (bsc#1254437)
* CVE-2025-13372: Potential SQL injection in FilteredRelation column
aliases on PostgreSQL
* CVE-2025-64460: Potential denial-of-service vulnerability in XML
Deserializer
* Fixed a crash on Python 3.14+ that prevented template tag functions
from being registered
* Fixed more bugs and regressions, see upstream release notes
-------------------------------------------------------------------
Mon Dec 8 11:04:00 UTC 2025 - Markéta Machová <mmachova@suse.com>
Thu Nov 6 07:20:08 UTC 2025 - Markéta Machová <mmachova@suse.com>
- Add security patches:
* CVE-2025-64459.patch (bsc#1252926)
* CVE-2025-13372.patch (bsc#1254437)
* CVE-2025-64460.patch (bsc#1254437)
- Update to 5.2.8 (bsc#1252926)
* CVE-2025-64459: Potential SQL injection via _connector keyword argument
* Added compatibility for oracledb 3.4.0
* Fixed a bug in Django 5.2 where QuerySet.first() and QuerySet.last()
raised an error on querysets performing aggregation that selected all
fields of a composite primary key.
-------------------------------------------------------------------
Thu Oct 2 10:01:50 UTC 2025 - Markéta Machová <mmachova@suse.com>
Thu Oct 2 09:41:30 UTC 2025 - Markéta Machová <mmachova@suse.com>
- Add security patches:
* CVE-2025-59681.patch (bsc#1250485)
* CVE-2025-59682.patch (bsc#1250487)
- Update to 5.2.7 (bsc#1250485, bsc#1250487)
* CVE-2025-59681: Potential SQL injection in QuerySet.annotate(), alias(),
aggregate(), and extra() on MySQL and MariaDB
* CVE-2025-59682: Potential partial directory-traversal via archive.extract()
* Fixed a regression in Django 5.2 that reduced the color contrast of the
label of filter_horizontal and filter_vertical widgets within a TabularInline
-------------------------------------------------------------------
Thu Sep 4 13:14:40 UTC 2025 - Markéta Machová <mmachova@suse.com>
Thu Sep 4 10:02:00 UTC 2025 - Markéta Machová <mmachova@suse.com>
- Add security patch CVE-2025-57833.patch (bsc#1248810)
- Update to 5.2.6 (bsc#1248810)
* CVE-2025-57833: Potential SQL injection in FilteredRelation column aliases
* Fixed a bug where using QuerySet.values() or values_list() with a ForeignObject
composed of multiple fields returned incorrect results instead of tuples of
the referenced fields
- Rebased test_strip_tags.patch
-------------------------------------------------------------------
Tue Aug 12 10:52:26 UTC 2025 - Markéta Machová <mmachova@suse.com>
- Update to 5.2.5
* Fixed a regression in Django 5.2.1 that prevented the usage of UNNEST
PostgreSQL strategy of QuerySet.bulk_create() with foreign keys
* Fixed a crash in Django 5.2 when filtering against a composite primary key
using a tuple containing expressions
* Fixed a crash in Django 5.2 when validating a model that uses
GeneratedField or constraints composed of Q and Case lookups
* Added compatibility for docutils 0.22
* Fixed a crash in Django 5.2 when using a ManyToManyField on a model with
a composite primary key, by extending the fields.E347 system check
- Convert to libalternatives on SLE-16-based and newer systems
-------------------------------------------------------------------
Fri Aug 1 02:09:01 UTC 2025 - Steve Kowalik <steven.kowalik@suse.com>
- Reinstate Requires on tzdata, a lot of packages use it.
-------------------------------------------------------------------
Wed Jul 30 06:03:44 UTC 2025 - Steve Kowalik <steven.kowalik@suse.com>
- Add patch support-msgfmt-0.25.patch:
* Support msgfmt 0.25 error messages changes. (bsc#1246966)
- Remove unneeded Requires on tzdata.
-------------------------------------------------------------------
Sat Jul 19 06:51:37 UTC 2025 - Markéta Machová <mmachova@suse.com>

View File

@@ -130,3 +130,55 @@ BzeRu0mAvjLkLNegQIvfdVXfYIcwUQQB8OAzoz3qzi8vji82MBQO+gkYrlteivoF
z+gZLcBuv/NdNg==
=B8gH
-----END PGP PUBLIC KEY BLOCK-----
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGV2bPUBEADQ/fSfsrdv/aGrUmyQ1ZmZNSDqlH8GXQH+LQiqOc5QjSgq09kv
YC5H3+yRmj4ENYJrD48Mce54k99U6LXhIopmPnCwnQjhpkXFqaZt8KtlhL/5VhIc
BFSodLkEFENWzpuxOkshOJYU8RxLO6MPQGKkokEGhxUHnNW5kwmGa93Iscu04SZR
L2pAHfYsnxB2Z5HAl4Os6iy7CKP8T5UQhGxwxojvjPyRJTb3IUDifjafPl4kbQyN
GdpgVnjHQtOzoVcXCsU4ig105IzydIf5BTYng3+idmlhQ1u8JzGETdlBvCtxg1K2
FmGD6IFzyvksdf44DNmxXmGnpMJK3y0+dpFzo33YBE3kFnYm57bwY55tdKmGnDyK
rR/1mARVOLQQYrDT6hgggNwRfqpTk28nU463LnXrLS89zaOxGQhfLm7w9kZ69WDd
UpI4RdZjA/klQJhPmLS7b8YmmZstvoZitsMgpPiexO+x7igMvWvuNyE8QL2EZ54K
6ojVObAdIbQe4LbiYCSut81F6e6tRsX3Jb46y5iUCZEkHvGsrZW6V1l/AIuYBecO
Xr5mmk5W4TpUlXTnyDFJr/FFtWfR1j2Z3ti+pZqAO4GUEUAjox+Dz/shKpmen7Qm
aj/Y/nuvD70B3c5XTWn62zTXVPYVX76sDnIuVsh/kORSP32OqMkesFKT9QARAQAB
tCdKYWNvYiBXYWxscyA8amFjb2J0eWxlcndhbGxzQGdtYWlsLmNvbT6JAlQEEwEI
AD4WIQRT1GlC4Aaio+7ci8gTFAP00W2NxwUCZXZs9QIbAwUJB4Ye+wULCQgHAgYV
CgkICwIEFgIDAQIeAQIXgAAKCRATFAP00W2NxwWVD/9ZTx8q3qsAXfOaWUNugaDM
oyUMa4bURfk82AXLVDju9fQQv/5wDlvQ+ejMDHE0pnvmQSa8bTol8y2IXO9Kl3ak
0NU3oOh3iJKk3q+FtxibGmIM5i095h2T97IBKJrZf6I1O0a9ZfMMDXbVpTeGIRDf
b55G6GG1jhdJa3SgtudUcGizlM0i7UrntUktmbnvWyYzbjWIKdYRq2W7vlEgKvNs
NZFVeO1KqLR92xUAIL+V7HtQ3TetyanYRH91Q8fZR9p+ScEOKJyfrpRa/IfrJKlg
k6MOqGeVaOd4nOX042S1+u0TU7HrVVVhL+Vru4utSYg9ZucM06wZOgR0A6Vay7Ap
Y43nGxRhc//auOJSh4nIQmtyup5+YiyKhynEnSF3pGuKXy64wVOO9XdW69bmyRxz
gAz4W5yTjmNBkQUYAGteJXo4fhDxWYzmeAIE7l5StTbgSZNLbndwmiWEyy8WzJtp
5xzluh+//39D+TUStZKG1MerhqnmJj3CBSTeeiKpW9QOtC4y2vuWzNp3XHS9FjeW
GOz5seJ5VDokkzP05XOfJ9SyblFAOW/Zteh2wdOb+S+/pUz+OukwPZthMwRsl0Dc
U+fx2mK+9I9TBJWgUHO1aXm6FlcyDnSlzc5oM4wXKF4sqByjjuCtYzK8UtEwlKNm
Pyboq4Y5RhYrW63LDl+SerkCDQRldmz1ARAAvkmAuvLXwZ86mubWYQFsmy3UKxdF
cewS8XikjsEZPvf0kYgi9wlMTRDvWlEmNGp9r4NWH36zm6uBjK9hxn+rNsiqkYez
MPTgwvxruSJWJPaFqK096Y8AMuk9SobMsydtlxuChQ3RwYo+j/v1csAdD88igzjW
3HKCzJ0e76ILdfbqlh8tJ+nbXvFgVVV2+vV996PTp4dQedc4/VzFGv00Mejkmhd3
nUBqdmG9mgqR4LK5xWCHXSVqgdVeH0urOiEN57aOie/30q6IegkfrKCSq1zGJE5o
WApzrOqo0UEDDND6PXDuLvzVuot3M/YvKjUXx8FatJC5BLSH0I9AKBWqPGvjk07V
IoI0CexyKoJKIkGl/ZUyRsorqmqg1671HUYNAFEbIwxeJUQkjTwqUxGXSjei7t8a
wzuZsA81lhXpwt6DfuQQ9XkDrc4dfyEuvkV9PforZxz8dVlDiORw3hPYqBjbU0hy
6D4Ytq3f88azYvZF8GE4uSYzvN6XeRhLr1hnfAFfGQFdNwg4qufjo8QYoQsYe6RZ
uenL1z5vkGRUz03l11GiaOqEJDJTy3dGACeQqAjjno8UL9Nb7PUWfnZL41zOGL8s
DHee7hX2+hsRPMoiqCzJrCjU52G5qGEB8d44VjNM8y435jyfBAh8ugv4rsSonzy+
ASe0msXK6bLxAy0AEQEAAYkCPAQYAQgAJhYhBFPUaULgBqKj7tyLyBMUA/TRbY3H
BQJldmz1AhsMBQkHhh77AAoJEBMUA/TRbY3HlA0QAI4DQ3MNfkpIZw/aKEetCaVB
LIVEfugoDcUQxiwc00G1aEuxpBdnfT5wFzUodUFJuIntGcYUMJwvAC3RB4ZANuoP
F2j/2JIp42GYhBjNSyy0ix/E2C/95HZDzsvDB4EZ0VMvec+MgAweTZhxZfdCsmId
lMWFfn3UM5FhtsOqLmw11T/6PdDgcyomrsp4ghHph9dJvMGiqXjysTSg3OMxzQeU
lzjX+6C4dY6AcpqAkeG+zRUYSQKsHQwSOxK7zon/hAA2+Gbvu+pzr0jHnatX8Fkd
eOBc6xZpnoQcubj/PXEBhXV4868jW/RNRG5ZXoQpnf63R0616JePkUKnXxs61JD9
oBuERUPn+HK346QNgNDRhzAufPYsXlPOPtU6poZqinMhsbcpW5Konjp2yLVwosXn
UPGLzZZHy9csF3VcWV96bM8WUEkFNfflisi1nE7yqC/CKgUjK0HUFDhyNHuCICTC
e9tvw2epbaLkid0NxV+a0RWWiUq2kMeTBUezVC27P+LAZ8aTZxHPyERtJlBgTCfm
1hWJ3CcIc4LT54AS3allzTv0f6iHX9WpJhfr3WPIY1Xt/d466l3VSofktwmpTEr2
DAECOit/CE1rFy1C+MHinAUX6C7ajg6msIE6uZHFrbHlSU8/WrbvZ1e4Nc7w7e/N
R182g26K1DaCZphtoq8Z
=0YqB
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -1,7 +1,7 @@
#
# spec file for package python-Django
#
# Copyright (c) 2025 SUSE LLC
# Copyright (c) 2026 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -20,8 +20,13 @@
%bcond_with selenium
%bcond_with memcached
%{?sle15_python_module_pythons}
%if 0%{?suse_version} > 1500
%bcond_without libalternatives
%else
%bcond_with libalternatives
%endif
Name: python-Django
Version: 5.2.4
Version: 5.2.10
Release: 0
Summary: A high-level Python Web framework
License: BSD-3-Clause
@@ -30,37 +35,11 @@ Source: https://www.djangoproject.com/m/releases/5.2/django-%{version}.t
Source1: https://www.djangoproject.com/m/pgp/Django-%{version}.checksum.txt
Source2: %{name}.keyring
Source99: python-Django-rpmlintrc
# PATCH-FIX-UPSTREAM https://github.com/django/django/pull/20390 Refs #36499 -- Adjusted test_strip_tags following Python behavior change for incomplete entities.
Patch100: test_strip_tags_incomplete.patch
# PATCH-FIX-UPSTREAM https://github.com/django/django/pull/19530 Fixed #36421 -- Made test_msgfmt_error_including_non_ascii compatible with with msgfmt 0.25.
Patch1: support-msgfmt-0.25.patch
# PATCH-FIX-UPSTREAM https://github.com/django/django/pull/19639 Fixed #36499 -- Adjusted utils_tests.test_html.TestUtilsHtml.test_strip_tags following Python's HTMLParser new behavior.
# fixed and refined upstream, but some of our interpreters weren't updated to a new version yet and still only carry the patch, so providing the non-conditional version
Patch101: test_strip_tags.patch
# PATCH-FIX-UPSTREAM CVE-2025-57833.patch bsc#1248810
Patch1: CVE-2025-57833.patch
# PATCH-FIX-UPSTREAM CVE-2025-59681.patch bsc#1250485
Patch2: CVE-2025-59681.patch
# PATCH-FIX-UPSTREAM CVE-2025-59682.patch bsc#1250487
Patch3: CVE-2025-59682.patch
# PATCH-FIX-UPSTREAM CVE-2025-64459.patch bsc#1252926
Patch4: CVE-2025-64459.patch
# PATCH-FIX-UPSTREAM CVE-2025-13372.patch bsc#1254437
Patch5: CVE-2025-13372.patch
# PATCH-FIX-UPSTREAM CVE-2025-64460.patch bsc#1254437
Patch6: CVE-2025-64460.patch
# PATCH-FIX-UPSTREAM CVE-2025-13473.patch bsc#1257401
Patch7: CVE-2025-13473.patch
# PATCH-FIX-UPSTREAM CVE-2025-14550.patch bsc#1257403
Patch8: CVE-2025-14550.patch
# PATCH-FIX-UPSTREAM CVE-2026-1207.patch bsc#1257405
Patch9: CVE-2026-1207.patch
# PATCH-FIX-UPSTREAM CVE-2026-1285.patch bsc#1257406
Patch10: CVE-2026-1285.patch
# PATCH-FIX-UPSTREAM CVE-2026-1287.patch bsc#1257407
Patch11: CVE-2026-1287.patch
# PATCH-FIX-UPSTREAM CVE-2026-1312.patch bsc#1257408
Patch12: CVE-2026-1312.patch
# PATCH-FIX-UPSTREAM CVE-2026-1312-followup.patch bsc#1257408
Patch13: CVE-2026-1312-followup.patch
Patch2: test_strip_tags.patch
BuildRequires: %{python_module Jinja2 >= 2.9.2}
BuildRequires: %{python_module Pillow >= 6.2.0}
BuildRequires: %{python_module PyYAML}
@@ -85,8 +64,6 @@ Requires: python
Requires: python-asgiref >= 3.7.0
Requires: python-sqlparse >= 0.3.1
Requires: python-tzdata
Requires(post): update-alternatives
Requires(postun): update-alternatives
Recommends: python-Jinja2 >= 2.9.2
Recommends: python-Pillow >= 6.2.0
Recommends: python-PyYAML
@@ -100,6 +77,13 @@ Obsoletes: python-django < %{version}
Provides: python-South = %{version}
Obsoletes: python-South < %{version}
BuildArch: noarch
%if %{with libalternatives}
BuildRequires: alts
Requires: alts
%else
Requires(post): update-alternatives
Requires(postun): update-alternatives
%endif
%if %{with memcached}
BuildRequires: %{python_module pylibmc}
BuildRequires: %{python_module pymemcache}
@@ -160,6 +144,9 @@ export PATH=%{_libdir}/chromium:$PATH
%python_expand PYTHONPATH=.:%{buildroot}%{$python_sitelib} $python tests/runtests.py -v 2
%endif
%pre
%python_libalternatives_reset_alternative django-admin
%post
%{python_install_alternative django-admin}

69
support-msgfmt-0.25.patch Normal file
View File

@@ -0,0 +1,69 @@
From 3609c463a4cfc5a7e76f4d4ba008c5096b1f1437 Mon Sep 17 00:00:00 2001
From: Jericho Serrano <118679068+jericho1050@users.noreply.github.com>
Date: Fri, 6 Jun 2025 04:58:29 +0800
Subject: [PATCH] Fixed #36421 -- Made test_msgfmt_error_including_non_ascii
compatible with msgfmt 0.25.
---
tests/i18n/test_compilation.py | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/tests/i18n/test_compilation.py b/tests/i18n/test_compilation.py
index 4b0bb9f6bb1..3a57dbf0765 100644
--- a/tests/i18n/test_compilation.py
+++ b/tests/i18n/test_compilation.py
@@ -1,5 +1,6 @@
import gettext as gettext_module
import os
+import re
import stat
import unittest
from io import StringIO
@@ -8,10 +9,12 @@
from unittest import mock
from django.core.management import CommandError, call_command, execute_from_command_line
-from django.core.management.utils import find_command
+from django.core.management.utils import find_command, popen_wrapper
from django.test import SimpleTestCase, override_settings
from django.test.utils import captured_stderr, captured_stdout
from django.utils import translation
+from django.utils.encoding import DEFAULT_LOCALE_ENCODING
+from django.utils.functional import cached_property
from django.utils.translation import gettext
from .utils import RunInTmpDirMixin, copytree
@@ -254,6 +257,17 @@ def test_no_dirs_accidentally_skipped(self):
class CompilationErrorHandling(MessageCompilationTests):
+ @cached_property
+ def msgfmt_version(self):
+ # Note that msgfmt is installed via GNU gettext tools, hence the msgfmt
+ # version should align to gettext.
+ out, err, status = popen_wrapper(
+ ["msgfmt", "--version"],
+ stdout_encoding=DEFAULT_LOCALE_ENCODING,
+ )
+ m = re.search(r"(\d+)\.(\d+)\.?(\d+)?", out)
+ return tuple(int(d) for d in m.groups() if d is not None)
+
def test_error_reported_by_msgfmt(self):
# po file contains wrong po formatting.
with self.assertRaises(CommandError):
@@ -278,7 +292,14 @@ def test_msgfmt_error_including_non_ascii(self):
call_command(
"compilemessages", locale=["ko"], stdout=StringIO(), stderr=stderr
)
- self.assertIn("' cannot start a field name", stderr.getvalue())
+ if self.msgfmt_version < (0, 25):
+ error_msg = "' cannot start a field name"
+ else:
+ error_msg = (
+ "a field name starts with a character that is not alphanumerical "
+ "or underscore"
+ )
+ self.assertIn(error_msg, stderr.getvalue())
class ProjectAndAppTests(MessageCompilationTests):

View File

@@ -10,10 +10,10 @@ Subject: [PATCH] Fixed #36499 -- Adjusted
tests/utils_tests/test_html.py | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
Index: django-5.2.4/tests/test_utils/tests.py
Index: django-5.2.9/tests/test_utils/tests.py
===================================================================
--- django-5.2.4.orig/tests/test_utils/tests.py
+++ django-5.2.4/tests/test_utils/tests.py
--- django-5.2.9.orig/tests/test_utils/tests.py
+++ django-5.2.9/tests/test_utils/tests.py
@@ -945,10 +945,10 @@ class HTMLEqualTests(SimpleTestCase):
self.assertHTMLEqual("", "<p>")
error_msg = (
@@ -22,25 +22,28 @@ Index: django-5.2.4/tests/test_utils/tests.py
+ "('Unexpected end tag `div` (Line 1, Column 0)', (1, 0))"
)
with self.assertRaisesMessage(AssertionError, error_msg):
- self.assertHTMLEqual("< div></ div>", "<div></div>")
- self.assertHTMLEqual("< div></div>", "<div></div>")
+ self.assertHTMLEqual("</div>", "<div></div>")
with self.assertRaises(HTMLParseError):
parse_html("</p>")
Index: django-5.2.4/tests/utils_tests/test_html.py
Index: django-5.2.9/tests/utils_tests/test_html.py
===================================================================
--- django-5.2.4.orig/tests/utils_tests/test_html.py
+++ django-5.2.4/tests/utils_tests/test_html.py
@@ -144,10 +144,10 @@ class TestUtilsHtml(SimpleTestCase):
("&gotcha&#;<>", "&gotcha&#;<>"),
("<sc<!-- -->ript>test<<!-- -->/script>", "ript>test"),
("<script>alert()</script>&h", "alert()&h;"),
- ("><!" + ("&" * 16000) + "D", "><!" + ("&" * 16000) + "D"),
+ ("><!" + ("&" * 16000) + "D", ">"),
--- django-5.2.9.orig/tests/utils_tests/test_html.py
+++ django-5.2.9/tests/utils_tests/test_html.py
@@ -187,13 +187,13 @@ class TestUtilsHtml(SimpleTestCase):
),
(
"><!" + ("&" * 16000) + "D",
- ">" if htmlparser_fixed_security else "><!" + ("&" * 16000) + "D",
+ ">",
),
("X<<<<br>br>br>br>X", "XX"),
("<" * 50 + "a>" * 50, ""),
- (">" + "<a" * 500 + "a", ">" + "<a" * 500 + "a"),
+ (">" + "<a" * 500 + "a", ">"),
(
">" + "<a" * 500 + "a",
- ">" if htmlparser_fixed_security else ">" + "<a" * 500 + "a",
+ ">",
),
("<a" * 49 + "a" * 951, "<a" * 49 + "a" * 951),
("<" + "a" * 1_002, "<" + "a" * 1_002),
)

View File

@@ -1,24 +0,0 @@
From 5ca0f62213911a77dd4a62e843db7e420cc98b78 Mon Sep 17 00:00:00 2001
From: Jacob Walls <jacobtylerwalls@gmail.com>
Date: Thu, 11 Dec 2025 08:44:19 -0500
Subject: [PATCH] [5.2.x] Refs #36499 -- Adjusted test_strip_tags following
Python behavior change for incomplete entities.
Backport of 7b80b2186300620931009fd62c2969f108fe7a62 from main.
---
tests/utils_tests/test_html.py | 35 +++++++++++++++++++++++++++++-----
1 file changed, 30 insertions(+), 5 deletions(-)
Index: django-5.2.4/tests/utils_tests/test_html.py
===================================================================
--- django-5.2.4.orig/tests/utils_tests/test_html.py
+++ django-5.2.4/tests/utils_tests/test_html.py
@@ -143,7 +144,7 @@ class TestUtilsHtml(SimpleTestCase):
# https://bugs.python.org/issue20288
("&gotcha&#;<>", "&gotcha&#;<>"),
("<sc<!-- -->ript>test<<!-- -->/script>", "ript>test"),
- ("<script>alert()</script>&h", "alert()h"),
+ ("<script>alert()</script>&h", "alert()&h;"),
("><!" + ("&" * 16000) + "D", "><!" + ("&" * 16000) + "D"),
("X<<<<br>br>br>br>X", "XX"),
("<" * 50 + "a>" * 50, ""),