XSS

HIGH
django/django
Commit: 074198756859
Affected: Django 5.1.x (stable/5.1.x) prior to this commit 07419875685997a30cd281396e0dc867e98aefe3
2026-04-05 13:33 UTC

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):
← Back to Alerts View on GitHub →