summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/utils/text.py9
-rw-r--r--docs/releases/4.2.28.txt12
-rw-r--r--docs/releases/5.2.11.txt12
-rw-r--r--tests/utils_tests/test_text.py10
4 files changed, 39 insertions, 4 deletions
diff --git a/django/utils/text.py b/django/utils/text.py
index 26edde99e3..21efb00b98 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"</{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/docs/releases/4.2.28.txt b/docs/releases/4.2.28.txt
index aa06882806..6ff358a8ec 100644
--- a/docs/releases/4.2.28.txt
+++ b/docs/releases/4.2.28.txt
@@ -41,3 +41,15 @@ As a reminder, all untrusted user input should be validated before use.
This issue has severity "high" according to the :ref:`Django security policy
<security-disclosure>`.
+
+CVE-2026-1285: Potential denial-of-service vulnerability in ``django.utils.text.Truncator`` HTML methods
+========================================================================================================
+
+``django.utils.text.Truncator.chars()`` and ``Truncator.words()`` methods (with
+``html=True``) and the :tfilter:`truncatechars_html` and
+:tfilter:`truncatewords_html` template filters were subject to a potential
+denial-of-service attack via certain inputs with a large number of unmatched
+HTML end tags, which could cause quadratic time complexity during HTML parsing.
+
+This issue has severity "moderate" according to the :ref:`Django security
+policy <security-disclosure>`.
diff --git a/docs/releases/5.2.11.txt b/docs/releases/5.2.11.txt
index 73a0cd23b3..bc5fb02063 100644
--- a/docs/releases/5.2.11.txt
+++ b/docs/releases/5.2.11.txt
@@ -41,3 +41,15 @@ As a reminder, all untrusted user input should be validated before use.
This issue has severity "high" according to the :ref:`Django security policy
<security-disclosure>`.
+
+CVE-2026-1285: Potential denial-of-service vulnerability in ``django.utils.text.Truncator`` HTML methods
+========================================================================================================
+
+``django.utils.text.Truncator.chars()`` and ``Truncator.words()`` methods (with
+``html=True``) and the :tfilter:`truncatechars_html` and
+:tfilter:`truncatewords_html` template filters were subject to a potential
+denial-of-service attack via certain inputs with a large number of unmatched
+HTML end tags, which could cause quadratic time complexity during HTML parsing.
+
+This issue has severity "moderate" according to the :ref:`Django security
+policy <security-disclosure>`.
diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py
index 63c7889cbc..11c01874cb 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("<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(