diff --git a/CHANGES.md b/CHANGES.md
index 23455420..cabaeb1f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -21,6 +21,7 @@
- [pull #728] Fix XSS from zero-padded colon entities in link URLs (#726)
- [pull #734] Fix empty list items being parsed as nested lists (#167)
- [pull #735] Fix `smarty-pants` extra converting quotes inside inline HTML tags and comments (#150)
+- [pull #737] Fix XSS from code spans that the span gamut doesn't treat as code spans in safe mode (#726)
## python-markdown2 2.5.5
diff --git a/lib/markdown2.py b/lib/markdown2.py
index 2da6ff29..18419ef0 100755
--- a/lib/markdown2.py
+++ b/lib/markdown2.py
@@ -1448,10 +1448,17 @@ def _is_comment(token):
return re.match(r'()', token)
# protect raw code spans from processing, as they can often contain anything that looks like HTML and
- # trips up the regex. These are encoded and processed later on anyway
+ # trips up the regex. These are encoded and processed later on anyway.
+ # The span gamut doesn't always agree that these are code spans
+ # (eg: a tag spanning lines changes how backticks pair up), so swap
+ # any `<` for a hash that unescapes to `<`. Nothing in here can
+ # then come out as raw HTML (#726), and real code spans still only
+ # get escaped once.
code_hashes = {}
+ lt_hash = _hash_text('<')
+ self._code_table['<'] = lt_hash
text = self._code_span_re.sub(
- lambda m: self._hash_span(m.string[m.start(): m.end()], code_hashes),
+ lambda m: self._hash_span(m.group(0).replace('<', lt_hash), code_hashes),
text
)
@@ -1736,7 +1743,8 @@ def _h_sub(self, match: re.Match[str]) -> str:
n = min(n + demote_headers, 6)
header_id_attr = ""
if "header-ids" in self.extras:
- header_id = self.header_id_from_text(header_group,
+ header_id = self.header_id_from_text(
+ self._unescape_special_chars(header_group),
self.extras["header-ids"].get('prefix'), n)
if header_id:
header_id_attr = ' id="%s"' % header_id
@@ -3682,8 +3690,7 @@ def _code_block_with_lexer_sub(
formatter_opts = self.md.extras['fenced-code-blocks'] or {}
def unhash_code(codeblock):
- for key, sanitized in list(self.md.html_spans.items()):
- codeblock = codeblock.replace(key, sanitized)
+ codeblock = self.md._unhash_html_spans(codeblock, code=True)
replacements = [
("&", "&"),
("<", "<"),
diff --git a/test/tm-cases/safe_mode_code_spans_in_fenced_code_with_lexer.html b/test/tm-cases/safe_mode_code_spans_in_fenced_code_with_lexer.html
new file mode 100644
index 00000000..d9db860b
--- /dev/null
+++ b/test/tm-cases/safe_mode_code_spans_in_fenced_code_with_lexer.html
@@ -0,0 +1,4 @@
+
+
print`a<b`
+
+
diff --git a/test/tm-cases/safe_mode_code_spans_in_fenced_code_with_lexer.opts b/test/tm-cases/safe_mode_code_spans_in_fenced_code_with_lexer.opts
new file mode 100644
index 00000000..d5258749
--- /dev/null
+++ b/test/tm-cases/safe_mode_code_spans_in_fenced_code_with_lexer.opts
@@ -0,0 +1 @@
+{'safe_mode': 'escape', 'extras': ['fenced-code-blocks']}
\ No newline at end of file
diff --git a/test/tm-cases/safe_mode_code_spans_in_fenced_code_with_lexer.tags b/test/tm-cases/safe_mode_code_spans_in_fenced_code_with_lexer.tags
new file mode 100644
index 00000000..e2282d2f
--- /dev/null
+++ b/test/tm-cases/safe_mode_code_spans_in_fenced_code_with_lexer.tags
@@ -0,0 +1 @@
+fenced-code-blocks pygments
\ No newline at end of file
diff --git a/test/tm-cases/safe_mode_code_spans_in_fenced_code_with_lexer.text b/test/tm-cases/safe_mode_code_spans_in_fenced_code_with_lexer.text
new file mode 100644
index 00000000..4131fbb8
--- /dev/null
+++ b/test/tm-cases/safe_mode_code_spans_in_fenced_code_with_lexer.text
@@ -0,0 +1,3 @@
+```python
+print `a
+
+
+
a
+
b
+
+
+
+
+
`x
+
<svg onload=alert(origin)>`
+
+
+
diff --git a/test/tm-cases/xss_code_spans_in_tables_issue726.opts b/test/tm-cases/xss_code_spans_in_tables_issue726.opts
new file mode 100644
index 00000000..3da78cd0
--- /dev/null
+++ b/test/tm-cases/xss_code_spans_in_tables_issue726.opts
@@ -0,0 +1 @@
+{"safe_mode": "escape", "extras": ["tables"]}
diff --git a/test/tm-cases/xss_code_spans_in_tables_issue726.text b/test/tm-cases/xss_code_spans_in_tables_issue726.text
new file mode 100644
index 00000000..41e02416
--- /dev/null
+++ b/test/tm-cases/xss_code_spans_in_tables_issue726.text
@@ -0,0 +1,3 @@
+| a | b |
+|---|---|
+| `x |