Listen to this Post
The vulnerability arises in `justhtml` versions 0.9.0 through 1.21.0, where the `to_markdown()` function renders `` text (and `
` text inside a link) as an inline Markdown code span. The only protection applied is a backtick fence whose length is chosen to outlast any run of backticks inside the content – but the fence does not guard against block boundaries. Because `to_text(separator="", strip=False)` preserves embedded newlines, a blank line (\n\n) inside the code span terminates the inline span in any compliant Markdown renderer (CommonMark). The opening backticks become unmatched and are rendered literally, while everything after the blank line is parsed as ordinary Markdown. Since CommonMark passes raw inline HTML through by default, attacker‑controlled text such as `` that survived HTML sanitization is emitted unescaped and becomes a live DOM element, leading to cross‑site scripting.
The vulnerable code paths are in `src/justhtml/node.py`:
- `_markdown_code_span()` (lines 32‑41) only sizes the fence; it never checks for newlines or block boundaries. - The dispatch for `` (line 1078) and for `` inside a link (line 1064) both route the verbatim text into this inline helper. - By contrast, `` outside a link uses a block‑level fenced code block (minimum fence length 3) which is not broken by blank lines. This behaviour is reachable with default settings: `JustHTML(html)` sanitises by default, `` and `` are in the default policy, and the payload lives in text (not an attribute), so URL‑scheme sanitisation never applies. The tokenizer decodes character references before DOM insertion, so the malicious HTML passes sanitisation as literal text. Two in‑repo asymmetries confirm this is an unintended path: plain text nodes are HTML‑escaped before Markdown escaping, but code‑span bodies are not; and `` outside a link uses a safe block fence, while the same `` inside a link uses the vulnerable inline span. The provided Proof of Concept (PoC) shows that a single blank line changes an inert control into a live XSS payload. The control (<code>q) produces inert</code> <code>` output, while the exploit (`q\n\n) breaks out of the code span and renders the image tag with an executable event handler. The issue is deterministic and reproducible in a Docker environment.</code> DailyCVE Form:
Platform: justhtml Version: 0.9.0‑1.21.0 Vulnerability: XSS blank‑line breakout Severity: CVSS 6.1 Moderate date: 2026‑06‑25Prediction: 2026‑07‑15
What Undercode Say:
Analytics from the advisory and PoC:
Build and run the PoC container docker build -t justhtml-md-poc ./poc docker run --rm justhtml-md-poc Optional execution confirmation docker run --rm justhtml-md-poc python3 /poc/poc.py --prove-execKey code snippets from the PoC (`poc.py`):
PAYLOAD_TEXT = f"<img src=x onerror={MARKER}()>" control = f"<code>q{enc}</code>" no blank line → inert exploit = f"<code>q\n\n{enc}</code>" blank line → breakout def to_markdown(html: str) -> str: return JustHTML(html, fragment=True).to_markdown()Observed output (justhtml 1.21.0, markdown‑it‑py 4.2.0):
== CONTROL (payload in <code>, NO blank line) == to_markdown() out : '<code>q<img src=x onerror=__POC_XSS_MARKER__()></code>' CommonMark render : ' <code>q<img src=x onerror=__POC_XSS_MARKER__()></code> ' live JS sinks : NONE (inert) == EXPLOIT (payload in <code>, + blank line) == to_markdown() out : '<code>q\n\n<img src=x onerror=__POC_XSS_MARKER__()></code>' CommonMark render : ' `q \n <img src=x onerror=__POC_XSS_MARKER__()>` ' live JS sinks : [('img', 'onerror', '<strong>POC_XSS_MARKER</strong>()')]Exploit:
An attacker only needs to control the text content inside a `
` element, or a `` element that appears within a link. No custom policy or `sanitize=False` is required. By inserting a blank line (\n\n) followed by malicious HTML (e.g.,<img src=x onerror=alert(1)>), the attacker forces the Markdown renderer to close the inline code span early and re‑parse the subsequent text as raw HTML. When a victim views the rendered page, the attacker’s script executes in the victim’s origin, enabling session theft, cookie exfiltration, or arbitrary actions performed as the victim.Protection:
- Do not emit inline code spans for content containing a blank line. In `_markdown_code_span()` and the
<code>/in‑link `` dispatch, if the content contains `\n` (or any blank line), emit it as a fenced code block instead – reusing the existing block path (lines 1066‑1074) which is not broken by blank lines.- As defence‑in‑depth, HTML‑escape and Markdown‑escape special characters in code‑span bodies rather than relying on fence length alone, matching the escaping already applied to plain text nodes.
- Upgrade to a patched version once available (the issue is unfixed as of v1.21.0; monitor the repository for a security release).
Impact:
This is a cross‑site scripting vulnerability (CWE‑79) arising from improper output escaping (CWE‑116). It affects any application that follows the documented pipeline: sanitise untrusted HTML with `JustHTML(...)` (default settings), call
to_markdown(), and render the result with a CommonMark‑compliant renderer (raw‑HTML passthrough is the default). The CVSS v3.1 score is 6.1 (Moderate) with vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N. Scope is Changed because the injected script runs in the origin of the page that renders the Markdown – a different security authority than the library that produced it. Successful exploitation can lead to cookie/session theft, data exfiltration, or victim‑impersonated actions.🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by ThousandsSources:
Reported By: github.com
Extra Source Hub:
Undercode🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]
📢 Follow DailyCVE & Stay Tuned:

