From 3b96ec1c9e6974229796bc36aa9bc6fad6829b29 Mon Sep 17 00:00:00 2001
From: Crozzers
Date: Thu, 14 May 2026 22:23:12 +0100
Subject: [PATCH 1/3] Fix XSS fromn HTML encoded colons in hrefs
---
lib/markdown2.py | 4 +++-
test/tm-cases/xss_smuggling_spans_in_image_attrs.html | 2 ++
test/tm-cases/xss_smuggling_spans_in_image_attrs.text | 4 +++-
3 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/lib/markdown2.py b/lib/markdown2.py
index dc698970..6683df7c 100755
--- a/lib/markdown2.py
+++ b/lib/markdown2.py
@@ -1537,8 +1537,10 @@ def _safe_href(self):
safe = r'-\w'
# omitted ['"<>] for XSS reasons
less_safe = r'#/\.!#$%&\(\)\+,/:;=\?@\[\]^`\{\}\|~'
+ # html encoded colon in a URL still functions as a normal colon, so need to detect those
+ protocol_seperators = [':', ':', ':', ':']
# dot seperated hostname, optional port number, not followed by protocol seperator
- domain = r'(?:[{}]+(?:\.[{}]+)*)(?:(?![<code>" onerror="alert(1)//</code>]()

+
+x
diff --git a/test/tm-cases/xss_smuggling_spans_in_image_attrs.text b/test/tm-cases/xss_smuggling_spans_in_image_attrs.text
index 4a5c25a8..12d54edb 100644
--- a/test/tm-cases/xss_smuggling_spans_in_image_attrs.text
+++ b/test/tm-cases/xss_smuggling_spans_in_image_attrs.text
@@ -2,4 +2,6 @@
![`" onerror="alert(1)//`]()
-
\ No newline at end of file
+
+
+[x](javascript:alert(origin))
\ No newline at end of file
From a11ce82fbb99c3f8b72711a141ee1a511a90846b Mon Sep 17 00:00:00 2001
From: Crozzers
Date: Thu, 14 May 2026 22:24:25 +0100
Subject: [PATCH 2/3] Fix XSS from making javascript: hrefs look like domains
with ports
---
lib/markdown2.py | 2 +-
test/tm-cases/xss_smuggling_spans_in_image_attrs.html | 2 ++
test/tm-cases/xss_smuggling_spans_in_image_attrs.text | 4 +++-
3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/lib/markdown2.py b/lib/markdown2.py
index 6683df7c..745d91f6 100755
--- a/lib/markdown2.py
+++ b/lib/markdown2.py
@@ -1540,7 +1540,7 @@ def _safe_href(self):
# html encoded colon in a URL still functions as a normal colon, so need to detect those
protocol_seperators = [':', ':', ':', ':']
# dot seperated hostname, optional port number, not followed by protocol seperator
- domain = r'(?:[{}]+(?:\.[{}]+)*)(?:(?
x
+
+x
diff --git a/test/tm-cases/xss_smuggling_spans_in_image_attrs.text b/test/tm-cases/xss_smuggling_spans_in_image_attrs.text
index 12d54edb..26edae4e 100644
--- a/test/tm-cases/xss_smuggling_spans_in_image_attrs.text
+++ b/test/tm-cases/xss_smuggling_spans_in_image_attrs.text
@@ -4,4 +4,6 @@

-[x](javascript:alert(origin))
\ No newline at end of file
+[x](javascript:alert(origin))
+
+[x](javascript:1/alert(origin))
\ No newline at end of file
From 82b4482b70a1718eef9a4d4fb2449c059949a5f0 Mon Sep 17 00:00:00 2001
From: Crozzers
Date: Thu, 14 May 2026 22:39:11 +0100
Subject: [PATCH 3/3] Fix onerror XSS in image title attr
---
lib/markdown2.py | 2 ++
test/tm-cases/xss_smuggling_spans_in_image_attrs.html | 7 +++++++
test/tm-cases/xss_smuggling_spans_in_image_attrs.text | 5 ++++-
3 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/lib/markdown2.py b/lib/markdown2.py
index 745d91f6..4ba78a4f 100755
--- a/lib/markdown2.py
+++ b/lib/markdown2.py
@@ -3271,6 +3271,8 @@ def run(self, text: str):
.replace('*', self.md._escape_table['*'])
.replace('_', self.md._escape_table['_'])
)
+ if self.md.safe_mode:
+ title = self.md._hash_span(title)
title_str = f' title="{title}"'
else:
title_str = ''
diff --git a/test/tm-cases/xss_smuggling_spans_in_image_attrs.html b/test/tm-cases/xss_smuggling_spans_in_image_attrs.html
index 20e0cd4d..ccd398e7 100644
--- a/test/tm-cases/xss_smuggling_spans_in_image_attrs.html
+++ b/test/tm-cases/xss_smuggling_spans_in_image_attrs.html
@@ -7,3 +7,10 @@
x
x
+
+
+-
+
+
onerror=alert(origin) )
+
+
diff --git a/test/tm-cases/xss_smuggling_spans_in_image_attrs.text b/test/tm-cases/xss_smuggling_spans_in_image_attrs.text
index 26edae4e..3f025a00 100644
--- a/test/tm-cases/xss_smuggling_spans_in_image_attrs.text
+++ b/test/tm-cases/xss_smuggling_spans_in_image_attrs.text
@@ -6,4 +6,7 @@
[x](javascript:alert(origin))
-[x](javascript:1/alert(origin))
\ No newline at end of file
+[x](javascript:1/alert(origin))
+
+-
+-  onerror=alert(origin) )
\ No newline at end of file