diff --git a/docs/changelog.md b/docs/changelog.md index dcb63628..e4472506 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 ccf7924b..1db9b99e 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 2094203a..3284115b 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='">