From 5e208b3f7e559be65a6ac6f5f69fb84c4da9b24c Mon Sep 17 00:00:00 2001 From: Naaz nuria Date: Tue, 20 Oct 2026 19:17:18 +0530 Subject: [PATCH] Escape language name in codehilite non-Pygments output When Pygments is unavailable (or use_pygments is False), CodeHilite.hilite built the code element's class attribute from the language name without escaping it. With attr_list enabled the language comes from a fenced-block class token, whose scanner permits ", < and >, so a value could close the class attribute and inject markup. fenced_code already escapes the language in its own non-Pygments branch; apply the same escaping in codehilite's fallback. --- docs/changelog.md | 7 +++++++ markdown/extensions/codehilite.py | 3 ++- tests/test_syntax/extensions/test_code_hilite.py | 7 +++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index dcb636281..e44725063 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -12,6 +12,13 @@ and this project adheres to the [Python Version Specification](https://packaging.python.org/en/latest/specifications/version-specifiers/). See the [Contributing Guide](contributing.md) for details. +## [Unreleased] + +### Fixed + +* Escape the language name in the `codehilite` extension's non-Pygments output so + a language value cannot break out of the `class` attribute and inject markup. + ## [3.11.2] - 2026-10-19 ### Fixed diff --git a/markdown/extensions/codehilite.py b/markdown/extensions/codehilite.py index ccf7924b5..1db9b99e1 100644 --- a/markdown/extensions/codehilite.py +++ b/markdown/extensions/codehilite.py @@ -24,6 +24,7 @@ from . import Extension from ..treeprocessors import Treeprocessor from ..util import parseBoolValue +from ..serializers import _escape_attrib_html from typing import TYPE_CHECKING, Callable, Any if TYPE_CHECKING: # pragma: no cover @@ -179,7 +180,7 @@ def hilite(self, shebang: bool = True) -> str: txt = txt.replace('"', '"') classes = [] if self.lang: - classes.append('{}{}'.format(self.lang_prefix, self.lang)) + classes.append('{}{}'.format(self.lang_prefix, _escape_attrib_html(self.lang))) if self.options['linenos']: classes.append('linenums') class_str = '' diff --git a/tests/test_syntax/extensions/test_code_hilite.py b/tests/test_syntax/extensions/test_code_hilite.py index 2094203ae..3284115b9 100644 --- a/tests/test_syntax/extensions/test_code_hilite.py +++ b/tests/test_syntax/extensions/test_code_hilite.py @@ -140,6 +140,13 @@ def test_codehilite_use_pygments_false(self): ) self.assertOutputEquals('', expected, lang='php', use_pygments=False) + def test_codehilite_lang_escaped(self): + expected = ( + '
text\n'
+            '
' + ) + self.assertOutputEquals('text', expected, lang='">