XSS
Description
The commit fixes an XSS vulnerability in escapejs by escaping additional control characters in the JavaScript escaping map. Previously, escapejs only escaped ASCII control characters below 32 (and 0x7F range may not have been fully covered), leaving characters in the C0/C1 ranges (0x7F-0x9F) potentially unescaped and usable to bypass escaping and inject inline JavaScript or break out of intended contexts. The patch broadens escaping to include 0x7F-0x9F and updates tests to cover these code points, hardening escaping of user-provided data embedded into JavaScript contexts within templates and JSON.
Commit Details
Author: farthestmage
Date: 2025-11-17 10:26 UTC
Message:
Fixed #36737 -- Escaped further control characters in escapejs.
Triage Assessment
Vulnerability Type: XSS
Confidence: HIGH
Reasoning:
The commit broadens the escaping for escapejs to cover more control characters (C0/C1 ranges) that could previously bypass escaping and lead to XSS or JS injection. The diff shows changes to the escaping mapping and updated tests for additional control characters, indicating a security-focused hardening of escaping behavior.
Verification Assessment
Vulnerability Type: XSS
Confidence: HIGH
Affected Versions: Django 5.1.x (stable/5.1.x) prior to this commit 07419875685997a30cd281396e0dc867e98aefe3
Code Diff
diff --git a/django/utils/html.py b/django/utils/html.py
index 059767d394fd..68260af3372e 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -7,6 +7,7 @@
from collections import deque
from collections.abc import Mapping
from html.parser import HTMLParser
+from itertools import chain
from urllib.parse import parse_qsl, quote, unquote, urlencode, urlsplit, urlunsplit
from django.conf import settings
@@ -76,8 +77,11 @@ def escape(text):
ord("\u2029"): "\\u2029",
}
-# Escape every ASCII character with a value less than 32.
-_js_escapes.update((ord("%c" % z), "\\u%04X" % z) for z in range(32))
+# Escape every ASCII character with a value less than 32 (C0), 127(C0),
+# or 128-159(C1).
+_js_escapes.update(
+ (ord("%c" % z), "\\u%04X" % z) for z in chain(range(32), range(0x7F, 0xA0))
+)
@keep_lazy(SafeString)
diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py
index b388d3ce52a3..7412c2624c73 100644
--- a/tests/utils_tests/test_html.py
+++ b/tests/utils_tests/test_html.py
@@ -244,6 +244,9 @@ def test_escapejs(self):
"paragraph separator:\\u2029and line separator:\\u2028",
),
("`", "\\u0060"),
+ ("\u007f", "\\u007F"),
+ ("\u0080", "\\u0080"),
+ ("\u009f", "\\u009F"),
)
for value, output in items:
with self.subTest(value=value, output=output):