From c0843e91197b3efe15336ad0a3ea36d78642fbaf Mon Sep 17 00:00:00 2001 From: ze-you-liu Date: Sat, 28 Feb 2026 16:11:50 +0800 Subject: [PATCH 1/8] fix CVE-2026-1285 --- .gitignore | 2 + python-django-5.2.7-CVE-2026-1285.patch | 70 +++++++++++++++++++++++++ python-django.spec | 1 + 3 files changed, 73 insertions(+) create mode 100644 python-django-5.2.7-CVE-2026-1285.patch diff --git a/.gitignore b/.gitignore index cdf0f19..6f16c22 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /5.0.8.tar.gz + +django-5.2.7.tar.gz diff --git a/python-django-5.2.7-CVE-2026-1285.patch b/python-django-5.2.7-CVE-2026-1285.patch new file mode 100644 index 0000000..79195fc --- /dev/null +++ b/python-django-5.2.7-CVE-2026-1285.patch @@ -0,0 +1,70 @@ +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. + +Adapted-by: PkgAgent (modified to adapt to opencloudos-stream) + +--- + django/utils/text.py | 9 +++++---- + tests/utils_tests/test_text.py | 10 ++++++++++ + 2 files changed, 15 insertions(+), 4 deletions(-) + +diff --git a/django/utils/text.py b/django/utils/text.py +index 26edde9..21efb00 100644 +--- a/django/utils/text.py ++++ b/django/utils/text.py +@@ -126,10 +126,11 @@ class TruncateHTMLParser(HTMLParser): + def handle_endtag(self, tag): + if tag not in self.void_elements: + self.output += f"" +- 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 63c7889..11c0187 100644 +--- a/tests/utils_tests/test_text.py ++++ b/tests/utils_tests/test_text.py +@@ -202,6 +202,16 @@ class TestUtilsText(SimpleTestCase): + truncator = text.Truncator("

I <3 python, what about you?

") + self.assertEqual("

I <3 python, wh…

", 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 , the doesn't match , so all tags remain ++ # in the stack and are properly closed at truncation. ++ truncator = text.Truncator("XXXX") ++ self.assertEqual( ++ truncator.chars(2, html=True, truncate=""), ++ "XX", ++ ) ++ + def test_truncate_words(self): + truncator = text.Truncator("The quick brown fox jumped over the lazy dog.") + self.assertEqual( diff --git a/python-django.spec b/python-django.spec index 143a5ac..4ba15fb 100644 --- a/python-django.spec +++ b/python-django.spec @@ -15,6 +15,7 @@ Patch001: python-django-5.2.7-CVE-2025-64458.patch Patch002: python-django-5.2.7-CVE-2025-64459.patch Patch003: python-django-5.2.7-CVE-2025-64460.patch Patch004: python-django-5.2.7-CVE-2025-13372.patch +Patch005: python-django-5.2.7-CVE-2026-1285.patch BuildArch: noarch -- Gitee From 13b905f5b781c88912db24e16d4d1cdfe3e57ecf Mon Sep 17 00:00:00 2001 From: ze-you-liu Date: Sat, 28 Feb 2026 16:31:54 +0800 Subject: [PATCH 2/8] fix CVE-2026-1287 --- python-django-5.2.7-CVE-2026-1285.patch | 70 ------------------------- python-django.spec | 1 - 2 files changed, 71 deletions(-) delete mode 100644 python-django-5.2.7-CVE-2026-1285.patch diff --git a/python-django-5.2.7-CVE-2026-1285.patch b/python-django-5.2.7-CVE-2026-1285.patch deleted file mode 100644 index 79195fc..0000000 --- a/python-django-5.2.7-CVE-2026-1285.patch +++ /dev/null @@ -1,70 +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. - -Adapted-by: PkgAgent (modified to adapt to opencloudos-stream) - ---- - django/utils/text.py | 9 +++++---- - tests/utils_tests/test_text.py | 10 ++++++++++ - 2 files changed, 15 insertions(+), 4 deletions(-) - -diff --git a/django/utils/text.py b/django/utils/text.py -index 26edde9..21efb00 100644 ---- a/django/utils/text.py -+++ b/django/utils/text.py -@@ -126,10 +126,11 @@ class TruncateHTMLParser(HTMLParser): - def handle_endtag(self, tag): - if tag not in self.void_elements: - self.output += f"" -- 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 63c7889..11c0187 100644 ---- a/tests/utils_tests/test_text.py -+++ b/tests/utils_tests/test_text.py -@@ -202,6 +202,16 @@ class TestUtilsText(SimpleTestCase): - truncator = text.Truncator("

I <3 python, what about you?

") - self.assertEqual("

I <3 python, wh…

", 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 , the doesn't match , so all tags remain -+ # in the stack and are properly closed at truncation. -+ truncator = text.Truncator("XXXX") -+ self.assertEqual( -+ truncator.chars(2, html=True, truncate=""), -+ "XX", -+ ) -+ - def test_truncate_words(self): - truncator = text.Truncator("The quick brown fox jumped over the lazy dog.") - self.assertEqual( diff --git a/python-django.spec b/python-django.spec index 4ba15fb..143a5ac 100644 --- a/python-django.spec +++ b/python-django.spec @@ -15,7 +15,6 @@ Patch001: python-django-5.2.7-CVE-2025-64458.patch Patch002: python-django-5.2.7-CVE-2025-64459.patch Patch003: python-django-5.2.7-CVE-2025-64460.patch Patch004: python-django-5.2.7-CVE-2025-13372.patch -Patch005: python-django-5.2.7-CVE-2026-1285.patch BuildArch: noarch -- Gitee From 8e3038397c98d86e5c09d68f2e5251e935db5c98 Mon Sep 17 00:00:00 2001 From: ze-you-liu Date: Mon, 2 Mar 2026 15:06:51 +0800 Subject: [PATCH 3/8] fix CVE-2026-1285 --- python-django-5.2.7-CVE-2026-1285.patch | 70 +++++++++++++++++++++++++ python-django.spec | 1 + 2 files changed, 71 insertions(+) create mode 100644 python-django-5.2.7-CVE-2026-1285.patch diff --git a/python-django-5.2.7-CVE-2026-1285.patch b/python-django-5.2.7-CVE-2026-1285.patch new file mode 100644 index 0000000..79195fc --- /dev/null +++ b/python-django-5.2.7-CVE-2026-1285.patch @@ -0,0 +1,70 @@ +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. + +Adapted-by: PkgAgent (modified to adapt to opencloudos-stream) + +--- + django/utils/text.py | 9 +++++---- + tests/utils_tests/test_text.py | 10 ++++++++++ + 2 files changed, 15 insertions(+), 4 deletions(-) + +diff --git a/django/utils/text.py b/django/utils/text.py +index 26edde9..21efb00 100644 +--- a/django/utils/text.py ++++ b/django/utils/text.py +@@ -126,10 +126,11 @@ class TruncateHTMLParser(HTMLParser): + def handle_endtag(self, tag): + if tag not in self.void_elements: + self.output += f"" +- 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 63c7889..11c0187 100644 +--- a/tests/utils_tests/test_text.py ++++ b/tests/utils_tests/test_text.py +@@ -202,6 +202,16 @@ class TestUtilsText(SimpleTestCase): + truncator = text.Truncator("

I <3 python, what about you?

") + self.assertEqual("

I <3 python, wh…

", 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 , the doesn't match , so all tags remain ++ # in the stack and are properly closed at truncation. ++ truncator = text.Truncator("XXXX") ++ self.assertEqual( ++ truncator.chars(2, html=True, truncate=""), ++ "XX", ++ ) ++ + def test_truncate_words(self): + truncator = text.Truncator("The quick brown fox jumped over the lazy dog.") + self.assertEqual( diff --git a/python-django.spec b/python-django.spec index 143a5ac..4ba15fb 100644 --- a/python-django.spec +++ b/python-django.spec @@ -15,6 +15,7 @@ Patch001: python-django-5.2.7-CVE-2025-64458.patch Patch002: python-django-5.2.7-CVE-2025-64459.patch Patch003: python-django-5.2.7-CVE-2025-64460.patch Patch004: python-django-5.2.7-CVE-2025-13372.patch +Patch005: python-django-5.2.7-CVE-2026-1285.patch BuildArch: noarch -- Gitee From fd0eeddcd864eb2ffcc37d20db1f487a4dc03c7e Mon Sep 17 00:00:00 2001 From: ze-you-liu Date: Mon, 2 Mar 2026 15:51:27 +0800 Subject: [PATCH 4/8] fix CVE-2026-1287 --- python-django-5.2.7-CVE-2026-1287.patch | 302 ++++++++++++++++++++++++ python-django.spec | 1 + 2 files changed, 303 insertions(+) create mode 100644 python-django-5.2.7-CVE-2026-1287.patch diff --git a/python-django-5.2.7-CVE-2026-1287.patch b/python-django-5.2.7-CVE-2026-1287.patch new file mode 100644 index 0000000..8a1a329 --- /dev/null +++ b/python-django-5.2.7-CVE-2026-1287.patch @@ -0,0 +1,302 @@ +From 3e68ccdc11c127758745ddf0b4954990b14892bc Mon Sep 17 00:00:00 2001 +From: Jake Howard +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. + +Adapted-by: PkgAgent (modified to adapt to opencloudos-stream) + +--- + django/db/models/sql/query.py | 10 ++-- + tests/aggregation/tests.py | 18 +++++-- + tests/annotations/tests.py | 74 +++++++++++++++++++---------- + tests/expressions/test_queryset_values.py | 36 +++++++++----- + tests/queries/tests.py | 18 +++++-- + 5 files changed, 102 insertions(+), 54 deletions(-) + +diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py +index 3a1cd73..baeac3a 100644 +--- a/django/db/models/sql/query.py ++++ b/django/db/models/sql/query.py +@@ -48,9 +48,11 @@ from django.utils.tree import Node + + __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 @@ class Query(BaseExpression): + 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 2e41f19..f621c53 100644 +--- a/tests/aggregation/tests.py ++++ b/tests/aggregation/tests.py +@@ -2,6 +2,7 @@ import datetime + 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 @@ class AggregateTestCase(TestCase): + 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 78e5408..0430c68 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 @@ class NonAggregateAnnotationTestCase(TestCase): + ) + + 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 @@ class NonAggregateAnnotationTestCase(TestCase): + "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 @@ class AliasTests(TestCase): + 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 080ee06..afd8a51 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 @@ 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, 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 ffaabf4..ce7fd59 100644 +--- a/tests/queries/tests.py ++++ b/tests/queries/tests.py +@@ -2,6 +2,7 @@ import datetime + 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 @@ 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, 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. diff --git a/python-django.spec b/python-django.spec index 4ba15fb..5aa52de 100644 --- a/python-django.spec +++ b/python-django.spec @@ -16,6 +16,7 @@ Patch002: python-django-5.2.7-CVE-2025-64459.patch Patch003: python-django-5.2.7-CVE-2025-64460.patch Patch004: python-django-5.2.7-CVE-2025-13372.patch Patch005: python-django-5.2.7-CVE-2026-1285.patch +Patch006: python-django-5.2.7-CVE-2026-1287.patch BuildArch: noarch -- Gitee From 3a1c2245b63e768f1aa1c00314e49e17e48da55c Mon Sep 17 00:00:00 2001 From: ze-you-liu Date: Mon, 2 Mar 2026 15:51:30 +0800 Subject: [PATCH 5/8] fix CVE-2026-1285, CVE-2026-1287 --- python-django-5.2.7-CVE-2026-1285.patch | 70 ------ python-django-5.2.7-CVE-2026-1287.patch | 302 ------------------------ python-django.spec | 4 +- 3 files changed, 1 insertion(+), 375 deletions(-) delete mode 100644 python-django-5.2.7-CVE-2026-1285.patch delete mode 100644 python-django-5.2.7-CVE-2026-1287.patch diff --git a/python-django-5.2.7-CVE-2026-1285.patch b/python-django-5.2.7-CVE-2026-1285.patch deleted file mode 100644 index 79195fc..0000000 --- a/python-django-5.2.7-CVE-2026-1285.patch +++ /dev/null @@ -1,70 +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. - -Adapted-by: PkgAgent (modified to adapt to opencloudos-stream) - ---- - django/utils/text.py | 9 +++++---- - tests/utils_tests/test_text.py | 10 ++++++++++ - 2 files changed, 15 insertions(+), 4 deletions(-) - -diff --git a/django/utils/text.py b/django/utils/text.py -index 26edde9..21efb00 100644 ---- a/django/utils/text.py -+++ b/django/utils/text.py -@@ -126,10 +126,11 @@ class TruncateHTMLParser(HTMLParser): - def handle_endtag(self, tag): - if tag not in self.void_elements: - self.output += f"" -- 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 63c7889..11c0187 100644 ---- a/tests/utils_tests/test_text.py -+++ b/tests/utils_tests/test_text.py -@@ -202,6 +202,16 @@ class TestUtilsText(SimpleTestCase): - truncator = text.Truncator("

I <3 python, what about you?

") - self.assertEqual("

I <3 python, wh…

", 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 , the doesn't match , so all tags remain -+ # in the stack and are properly closed at truncation. -+ truncator = text.Truncator("XXXX") -+ self.assertEqual( -+ truncator.chars(2, html=True, truncate=""), -+ "XX", -+ ) -+ - def test_truncate_words(self): - truncator = text.Truncator("The quick brown fox jumped over the lazy dog.") - self.assertEqual( diff --git a/python-django-5.2.7-CVE-2026-1287.patch b/python-django-5.2.7-CVE-2026-1287.patch deleted file mode 100644 index 8a1a329..0000000 --- a/python-django-5.2.7-CVE-2026-1287.patch +++ /dev/null @@ -1,302 +0,0 @@ -From 3e68ccdc11c127758745ddf0b4954990b14892bc Mon Sep 17 00:00:00 2001 -From: Jake Howard -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. - -Adapted-by: PkgAgent (modified to adapt to opencloudos-stream) - ---- - django/db/models/sql/query.py | 10 ++-- - tests/aggregation/tests.py | 18 +++++-- - tests/annotations/tests.py | 74 +++++++++++++++++++---------- - tests/expressions/test_queryset_values.py | 36 +++++++++----- - tests/queries/tests.py | 18 +++++-- - 5 files changed, 102 insertions(+), 54 deletions(-) - -diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py -index 3a1cd73..baeac3a 100644 ---- a/django/db/models/sql/query.py -+++ b/django/db/models/sql/query.py -@@ -48,9 +48,11 @@ from django.utils.tree import Node - - __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 @@ class Query(BaseExpression): - 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 2e41f19..f621c53 100644 ---- a/tests/aggregation/tests.py -+++ b/tests/aggregation/tests.py -@@ -2,6 +2,7 @@ import datetime - 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 @@ class AggregateTestCase(TestCase): - 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 78e5408..0430c68 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 @@ class NonAggregateAnnotationTestCase(TestCase): - ) - - 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 @@ class NonAggregateAnnotationTestCase(TestCase): - "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 @@ class AliasTests(TestCase): - 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 080ee06..afd8a51 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 @@ 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, 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 ffaabf4..ce7fd59 100644 ---- a/tests/queries/tests.py -+++ b/tests/queries/tests.py -@@ -2,6 +2,7 @@ import datetime - 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 @@ 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, 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. diff --git a/python-django.spec b/python-django.spec index 5aa52de..8a92815 100644 --- a/python-django.spec +++ b/python-django.spec @@ -4,7 +4,7 @@ Summary: A high-level Python Web framework Name: python-django Version: 5.2.7 -Release: 3%{?dist} +Release: 4%{?dist} License: BSD URL: https://www.djangoproject.com/ Source0: https://github.com/django/django/archive/refs/tags/django-%{version}.tar.gz @@ -15,8 +15,6 @@ Patch001: python-django-5.2.7-CVE-2025-64458.patch Patch002: python-django-5.2.7-CVE-2025-64459.patch Patch003: python-django-5.2.7-CVE-2025-64460.patch Patch004: python-django-5.2.7-CVE-2025-13372.patch -Patch005: python-django-5.2.7-CVE-2026-1285.patch -Patch006: python-django-5.2.7-CVE-2026-1287.patch BuildArch: noarch -- Gitee From d85aeda63e18891c6f5a756a44aaed7911922bc9 Mon Sep 17 00:00:00 2001 From: ze-you-liu Date: Mon, 2 Mar 2026 23:30:45 +0800 Subject: [PATCH 6/8] fix CVE-2026-1285 --- python-django-5.2.7-CVE-2026-1285.patch | 70 +++++++++++++++++++++++++ python-django.spec | 1 + 2 files changed, 71 insertions(+) create mode 100644 python-django-5.2.7-CVE-2026-1285.patch diff --git a/python-django-5.2.7-CVE-2026-1285.patch b/python-django-5.2.7-CVE-2026-1285.patch new file mode 100644 index 0000000..79195fc --- /dev/null +++ b/python-django-5.2.7-CVE-2026-1285.patch @@ -0,0 +1,70 @@ +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. + +Adapted-by: PkgAgent (modified to adapt to opencloudos-stream) + +--- + django/utils/text.py | 9 +++++---- + tests/utils_tests/test_text.py | 10 ++++++++++ + 2 files changed, 15 insertions(+), 4 deletions(-) + +diff --git a/django/utils/text.py b/django/utils/text.py +index 26edde9..21efb00 100644 +--- a/django/utils/text.py ++++ b/django/utils/text.py +@@ -126,10 +126,11 @@ class TruncateHTMLParser(HTMLParser): + def handle_endtag(self, tag): + if tag not in self.void_elements: + self.output += f"" +- 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 63c7889..11c0187 100644 +--- a/tests/utils_tests/test_text.py ++++ b/tests/utils_tests/test_text.py +@@ -202,6 +202,16 @@ class TestUtilsText(SimpleTestCase): + truncator = text.Truncator("

I <3 python, what about you?

") + self.assertEqual("

I <3 python, wh…

", 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 , the doesn't match , so all tags remain ++ # in the stack and are properly closed at truncation. ++ truncator = text.Truncator("XXXX") ++ self.assertEqual( ++ truncator.chars(2, html=True, truncate=""), ++ "XX", ++ ) ++ + def test_truncate_words(self): + truncator = text.Truncator("The quick brown fox jumped over the lazy dog.") + self.assertEqual( diff --git a/python-django.spec b/python-django.spec index 8a92815..118ecac 100644 --- a/python-django.spec +++ b/python-django.spec @@ -15,6 +15,7 @@ Patch001: python-django-5.2.7-CVE-2025-64458.patch Patch002: python-django-5.2.7-CVE-2025-64459.patch Patch003: python-django-5.2.7-CVE-2025-64460.patch Patch004: python-django-5.2.7-CVE-2025-13372.patch +Patch005: python-django-5.2.7-CVE-2026-1285.patch BuildArch: noarch -- Gitee From d41778d7be7ffe5996708a76e74586870c6b2141 Mon Sep 17 00:00:00 2001 From: ze-you-liu Date: Mon, 2 Mar 2026 23:57:52 +0800 Subject: [PATCH 7/8] fix CVE-2026-1287 --- python-django-5.2.7-CVE-2026-1287.patch | 302 ++++++++++++++++++++++++ python-django.spec | 1 + 2 files changed, 303 insertions(+) create mode 100644 python-django-5.2.7-CVE-2026-1287.patch diff --git a/python-django-5.2.7-CVE-2026-1287.patch b/python-django-5.2.7-CVE-2026-1287.patch new file mode 100644 index 0000000..8a1a329 --- /dev/null +++ b/python-django-5.2.7-CVE-2026-1287.patch @@ -0,0 +1,302 @@ +From 3e68ccdc11c127758745ddf0b4954990b14892bc Mon Sep 17 00:00:00 2001 +From: Jake Howard +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. + +Adapted-by: PkgAgent (modified to adapt to opencloudos-stream) + +--- + django/db/models/sql/query.py | 10 ++-- + tests/aggregation/tests.py | 18 +++++-- + tests/annotations/tests.py | 74 +++++++++++++++++++---------- + tests/expressions/test_queryset_values.py | 36 +++++++++----- + tests/queries/tests.py | 18 +++++-- + 5 files changed, 102 insertions(+), 54 deletions(-) + +diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py +index 3a1cd73..baeac3a 100644 +--- a/django/db/models/sql/query.py ++++ b/django/db/models/sql/query.py +@@ -48,9 +48,11 @@ from django.utils.tree import Node + + __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 @@ class Query(BaseExpression): + 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 2e41f19..f621c53 100644 +--- a/tests/aggregation/tests.py ++++ b/tests/aggregation/tests.py +@@ -2,6 +2,7 @@ import datetime + 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 @@ class AggregateTestCase(TestCase): + 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 78e5408..0430c68 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 @@ class NonAggregateAnnotationTestCase(TestCase): + ) + + 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 @@ class NonAggregateAnnotationTestCase(TestCase): + "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 @@ class AliasTests(TestCase): + 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 080ee06..afd8a51 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 @@ 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, 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 ffaabf4..ce7fd59 100644 +--- a/tests/queries/tests.py ++++ b/tests/queries/tests.py +@@ -2,6 +2,7 @@ import datetime + 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 @@ 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, 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. diff --git a/python-django.spec b/python-django.spec index 118ecac..9220c08 100644 --- a/python-django.spec +++ b/python-django.spec @@ -16,6 +16,7 @@ Patch002: python-django-5.2.7-CVE-2025-64459.patch Patch003: python-django-5.2.7-CVE-2025-64460.patch Patch004: python-django-5.2.7-CVE-2025-13372.patch Patch005: python-django-5.2.7-CVE-2026-1285.patch +Patch006: python-django-5.2.7-CVE-2026-1287.patch BuildArch: noarch -- Gitee From 3c5a1a719de3fee1f2127a5a60b6b12219e13624 Mon Sep 17 00:00:00 2001 From: ze-you-liu Date: Mon, 2 Mar 2026 23:57:56 +0800 Subject: [PATCH 8/8] fix CVE-2026-1285, CVE-2026-1287 --- python-django.spec | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python-django.spec b/python-django.spec index 9220c08..dc990e7 100644 --- a/python-django.spec +++ b/python-django.spec @@ -4,7 +4,7 @@ Summary: A high-level Python Web framework Name: python-django Version: 5.2.7 -Release: 4%{?dist} +Release: 5%{?dist} License: BSD URL: https://www.djangoproject.com/ Source0: https://github.com/django/django/archive/refs/tags/django-%{version}.tar.gz @@ -130,6 +130,10 @@ python3 runtests.py --settings=test_sqlite --verbosity=2 --parallel 1 %changelog +* Mon Mar 02 2026 ze-you-liu - 5.2.7-5 +- [Type] security +- [DESC] Fix CVE-2026-1285, CVE-2026-1287 + * Thu Dec 04 2025 Shop You - 5.2.7-3 - [Type] security - [DESC] Fix CVE-2025-64460 and CVE-2025-13372 -- Gitee