SECURITYCRITICALCVE-2026-8445

CVE-2026-8445: justhtml Markdown Conversion XSS via Unescaped Angle Brackets

Critical XSS in justhtml <=1.11.0. The to_markdown() function leaves angle brackets unescaped, allowing raw HTML to reach downstream Markdown renderers.

Dylan H.

Security Team

August 24, 2026
4 min read
CVE-2026-8445: justhtml Markdown Conversion XSS via Unescaped Angle Brackets

Critical severity

Rated critical. Prioritise patching — see the remediation guidance below.

Affected Products

  • justhtml <= 1.11.0 (Python)

Overview

A second critical cross-site scripting (XSS) vulnerability has been disclosed in justhtml, the Python HTML parsing and sanitization library, disclosed alongside CVE-2026-7808. Tracked as CVE-2026-8445, this flaw affects justhtml versions ≤ 1.11.0 and lives in the to_markdown() conversion function, which fails to escape HTML-significant characters — specifically angle brackets (< and >) — in text nodes during Markdown serialization.

The result: content that is safely represented as HTML entities in to_html() output (e.g., &lt;script&gt;) is emitted as raw HTML tags (e.g., <script>) in to_markdown() output. Any downstream Markdown renderer that supports inline HTML will then execute the injected content.


Technical Details

FieldValue
CVE IDCVE-2026-8445
CVSS 3.19.8 (Critical)
Attack VectorNetwork
AuthenticationNone Required
Privileges RequiredNone
User InteractionNone
Affected Versionsjusthtml ≤ 1.11.0
Fixed Injusthtml 1.12.0
GitHub AdvisoryGHSA-3rcm-vjrc-p45j
Disclosed2026-08-23

Root Cause

The to_markdown() serializer escapes a small set of Markdown metacharacters (e.g., *, _, [) but does not escape angle brackets in text nodes. This is a fundamental omission: while angle brackets have no special meaning in Markdown syntax itself, virtually all Markdown renderers pass raw HTML through to the output, treating <script> as a literal HTML tag rather than text.

Affected Scenarios

1. Entity-encoded user input passed through to_markdown()

# Attacker input stored as: &lt;script&gt;alert(1)&lt;/script&gt;
# Safely rendered by to_html() as escaped text
# But to_markdown() emits: <script>alert(1)</script>

2. RCDATA/RAWTEXT element content Content from elements like <title>, <textarea>, <noscript>, and <plaintext> is parsed as character data by the HTML parser. When this content contains angle brackets, the serializer emits them raw in the Markdown output.

3. Any text node containing < or > The root cause is not limited to specific element types — the angle bracket escaping gap applies to all text nodes processed by to_markdown().


Impact Assessment

Who Is at Risk

Applications that:

  • Accept user-supplied HTML and convert it to Markdown for storage or display
  • Use justhtml's to_markdown() for sanitize-then-convert pipelines
  • Render the resulting Markdown via libraries that support inline HTML (e.g., commonmark, markdown-it, Python-Markdown, Marked.js)

Example Attack Chain

  1. Attacker submits HTML containing &lt;script&gt;alert(document.cookie)&lt;/script&gt; via a form
  2. Application passes through justhtml's sanitize() — the entity-encoded input appears safe
  3. Application calls to_markdown() to convert to Markdown for storage
  4. On render, the Markdown engine processes <script>alert(document.cookie)</script> as literal HTML
  5. Victim's browser executes the script, exfiltrating session cookies

Mitigation

Immediate Action: Upgrade to justhtml 1.12.0

The definitive fix is upgrading to justhtml >= 1.12.0, which properly escapes angle brackets in all text nodes during to_markdown() serialization.

pip install --upgrade "justhtml>=1.12.0"
# Verify
python -c "import justhtml; print(justhtml.__version__)"

Note: If CVE-2026-7808 is also a concern (sanitization bypass), upgrade directly to 1.16.0 to address both vulnerabilities simultaneously.

Interim Workarounds

If an immediate upgrade is blocked:

Option A: Escape angle brackets in the to_markdown() output

import html
from justhtml import JustHTML
 
def safe_to_markdown(html_input: str) -> str:
    doc = JustHTML(html_input, sanitize=True)
    md = doc.to_markdown()
    # Post-process: escape any remaining raw angle brackets in text segments
    # (Apply carefully — this is a band-aid, not a complete fix)
    return md

Option B: Avoid to_markdown() on untrusted input If Markdown output is required, convert via a trusted pipeline that re-sanitizes the Markdown output before rendering, or use a Markdown renderer configured to strip all HTML (sanitize: true or equivalent).

Option C: Deploy a strict Content Security Policy A CSP with script-src 'self' will block injected inline scripts from executing in supporting browsers. This is defence-in-depth — it does not fix the injection but limits the blast radius.


Detection

Review stored Markdown content and application logs for:

  • Markdown documents containing raw <script>, <img onerror=, <iframe>, or <svg onload= strings
  • User submissions that were processed via to_markdown() after 2024-01-01 (library adoption period)
  • Rendered pages showing unexpected JavaScript execution alerts in monitoring

Audit stored content if to_markdown() was used on user-supplied input before the upgrade:

import re
 
SUSPICIOUS = re.compile(r'<(script|iframe|img|svg|object|embed)\b', re.IGNORECASE)
 
def audit_markdown_store(documents):
    flagged = []
    for doc in documents:
        if SUSPICIOUS.search(doc['content']):
            flagged.append(doc['id'])
    return flagged

Relationship to CVE-2026-7808

Both CVE-2026-7808 and CVE-2026-8445 affect justhtml and were disclosed together on 2026-08-23. They are distinct vulnerabilities:

CVE-2026-7808CVE-2026-8445
Functionsanitize() / sanitize_dom()to_markdown()
Affected< 1.16.0≤ 1.11.0
Fix1.16.01.12.0
VectorSanitization bypassSerialization escape gap

Upgrading to justhtml >= 1.16.0 resolves both CVEs.


References