GHSA-3RCM-VJRC-P45J
Vulnerability from github – Published: 2026-03-18 20:19 – Updated: 2026-03-18 20:19Summary
to_markdown() does not sufficiently escape text content that looks like HTML. As a result, untrusted input that is safe in to_html() can become raw HTML in Markdown output.
This is not specific to tokenizer raw-text states like <title>, <noscript>, or <plaintext>, although those states can trigger the behavior. The root cause is broader: Markdown text serialization leaves angle brackets unescaped in text nodes.
Details
When converting a parsed document to Markdown, text nodes are escaped for a small set of Markdown metacharacters, but HTML-significant characters such as < and > are preserved. That means content parsed as text, including entity-decoded text or text produced by RCDATA/RAWTEXT-style parsing, can be emitted into Markdown as raw HTML.
Examples of affected input include:
- Text produced from entity-decoded input such as
<script>...</script> - Text inside elements like
<title>,<textarea>,<noscript>(when parsed as raw text), and<plaintext>
This is distinct from actual <script> or <style> elements in the DOM. Those are already dropped by default in to_markdown() unless html_passthrough=True.
Proof of Concept
General case
```python from justhtml import JustHTML
doc = JustHTML("
<img src=x onerror=alert(1)>
", fragment=True)print(doc.to_html()) print() print(doc.to_markdown())
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.11.0"
},
"package": {
"ecosystem": "PyPI",
"name": "justhtml"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.12.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-79"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-18T20:19:56Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "## Summary\n\n`to_markdown()` does not sufficiently escape text content that looks like HTML. As a result, untrusted input that is safe in `to_html()` can become raw HTML in Markdown output.\n\nThis is not specific to tokenizer raw-text states like `\u003ctitle\u003e`, `\u003cnoscript\u003e`, or `\u003cplaintext\u003e`, although those states can trigger the behavior. The root cause is broader: Markdown text serialization leaves angle brackets unescaped in text nodes.\n\n## Details\n\nWhen converting a parsed document to Markdown, text nodes are escaped for a small set of Markdown metacharacters, but HTML-significant characters such as `\u003c` and `\u003e` are preserved. That means content parsed as text, including entity-decoded text or text produced by RCDATA/RAWTEXT-style parsing, can be emitted into Markdown as raw HTML.\n\nExamples of affected input include:\n\n- Text produced from entity-decoded input such as `\u0026lt;script\u0026gt;...\u0026lt;/script\u0026gt;`\n- Text inside elements like `\u003ctitle\u003e`, `\u003ctextarea\u003e`, `\u003cnoscript\u003e` (when parsed as raw text), and `\u003cplaintext\u003e`\n\nThis is distinct from actual `\u003cscript\u003e` or `\u003cstyle\u003e` elements in the DOM. Those are already dropped by default in `to_markdown()` unless `html_passthrough=True`.\n\n## Proof of Concept\n\n### General case\n\n```python\nfrom justhtml import JustHTML\n\ndoc = JustHTML(\"\u003cp\u003e\u0026lt;img src=x onerror=alert(1)\u0026gt;\u003c/p\u003e\", fragment=True)\n\nprint(doc.to_html())\nprint()\nprint(doc.to_markdown())",
"id": "GHSA-3rcm-vjrc-p45j",
"modified": "2026-03-18T20:19:56Z",
"published": "2026-03-18T20:19:56Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/EmilStenstrom/justhtml/security/advisories/GHSA-3rcm-vjrc-p45j"
},
{
"type": "PACKAGE",
"url": "https://github.com/EmilStenstrom/justhtml"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N",
"type": "CVSS_V4"
}
],
"summary": "JustHTML has a Sanitizer Bypass (in Markdown)"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.