{"id":"CVE-2026-8445","aliases":["EUVD-2026-64659","GHSA-3rcm-vjrc-p45j","GHSA-wf6m-786j-gxrr"],"status":"published","title":"justhtml before 1.12.0 Sanitizer Bypass via Markdown","description":"justhtml versions \u003c= 1.11.0 (fixed in 1.12.0) do not sufficiently escape HTML-significant characters (angle brackets) in text nodes when converting a parsed document to Markdown via to_markdown(). While a small set of Markdown metacharacters are escaped, characters such as \u003c and \u003e are preserved, so untrusted input that is safe in to_html() — including entity-decoded text (e.g. \u0026lt;script\u0026gt;) or text from RCDATA/RAWTEXT-parsed elements like \u003ctitle\u003e, \u003ctextarea\u003e, \u003cnoscript\u003e, and \u003cplaintext\u003e — can be emitted as raw HTML in the Markdown output, enabling a sanitizer bypass and potential cross-site scripting when that output is rendered.","severity":{"version":"4.0","score":9.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-08","score":0.00635,"percentile":0.50699,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-79"],"published":"2026-03-18T20:19:56Z","modified":"2026-10-28T02:31:02Z","fixes":[{"ecosystem":"PyPI","package":"justhtml","fixed_in":["1.12.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"justhtml","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"1.11.0"},{"fixed":"1.12.0"}]}]},{"source":"osv","ecosystem":"PyPI","package":"justhtml","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.12.0"}]}],"versions":["0.1.0","0.10.0","0.11.0","0.12.0","0.13.0","0.13.1","0.14.0","0.15.0","0.16.0","0.17.0","0.18.0","0.19.0","0.2.0","0.20.0","0.21.0","0.22.0","0.23.0","0.24.0","0.25.0","0.26.0","0.27.0","0.28.0","0.29.0","0.3.0","0.30.0","0.31.0","0.32.0","0.33.0","0.34.0","0.35.0","0.36.0","0.37.0","0.38.0","0.39.0","0.4.0","0.40.0","0.5.0","0.5.1","0.5.2","0.6.0","0.7.0","0.8.0","0.9.0","1.0.0","1.1.0","1.10.0","1.11.0","1.2.0","1.3.0","1.4.0","1.5.0","1.6.0","1.7.0","1.8.0","1.9.0","1.9.1"]}],"products":[{"vendor":"emilstenstrom","product":"justhtml"}],"references":[{"url":"https://github.com/EmilStenstrom/justhtml/security/advisories/GHSA-3rcm-vjrc-p45j","tags":["vendor-advisory","exploit","advisory","web"]},{"url":"https://www.vulncheck.com/advisories/justhtml-before-sanitizer-bypass-via-markdown","tags":["third-party-advisory","advisory"]},{"url":"https://github.com/advisories/GHSA-3rcm-vjrc-p45j"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-8445","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-wf6m-786j-gxrr"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/8xxx/CVE-2026-8445.json","tags":["advisory"]},{"url":"https://github.com/EmilStenstrom/justhtml","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-8445","title":"justhtml before 1.12.0 Sanitizer Bypass via Markdown","description":"justhtml versions \u003c= 1.11.0 (fixed in 1.12.0) do not sufficiently escape HTML-significant characters (angle brackets) in text nodes when converting a parsed document to Markdown via to_markdown(). While a small set of Markdown metacharacters are escaped, characters such as \u003c and \u003e are preserved, so untrusted input that is safe in to_html() — including entity-decoded text (e.g. \u0026lt;script\u0026gt;) or text from RCDATA/RAWTEXT-parsed elements like \u003ctitle\u003e, \u003ctextarea\u003e, \u003cnoscript\u003e, and \u003cplaintext\u003e — can be emitted as raw HTML in the Markdown output, enabling a sanitizer bypass and potential cross-site scripting when that output is rendered.","status":"published","published":"2026-08-23T13:34:13.886Z","modified":"2026-08-24T19:06:17.094Z","cvss":[{"version":"4.0","score":9.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical","assessor":"cna"},{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical","assessor":"cna"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T13:48:13.398044Z"}],"euvd":[{"id":"EUVD-2026-64659","description":"justhtml versions \u003c= 1.11.0 (fixed in 1.12.0) do not sufficiently escape HTML-significant characters (angle brackets) in text nodes when converting a parsed document to Markdown via to_markdown(). While a small set of Markdown metacharacters are escaped, characters such as \u003c and \u003e are preserved, so untrusted input that is safe in to_html() — including entity-decoded text (e.g. \u0026lt;script\u0026gt;) or text from RCDATA/RAWTEXT-parsed elements like \u003ctitle\u003e, \u003ctextarea\u003e, \u003cnoscript\u003e, and \u003cplaintext\u003e — can be emitted as raw HTML in the Markdown output, enabling a sanitizer bypass and potential cross-site scripting when that output is rendered.","status":"published","published":"2026-08-23T13:34:13Z","modified":"2026-08-24T19:06:17Z","cvss":[{"version":"4.0","score":9.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical"}],"updated_at":"2026-11-02T18:01:34.230875Z"}],"ghsa":[{"id":"GHSA-3rcm-vjrc-p45j","title":"JustHTML has a Sanitizer Bypass (in Markdown)","description":"## 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())","status":"published","published":"2026-03-18T20:19:56Z","modified":"2026-03-18T20:19:56Z","cvss":[{"version":"4.0","score":5.3,"vector":"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","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:39:37.554665Z"},{"id":"GHSA-wf6m-786j-gxrr","title":"justhtml versions \u003c= 1.11.0 (fixed in 1.12.0) do not sufficiently escape HTML-significant...","description":"justhtml versions \u003c= 1.11.0 (fixed in 1.12.0) do not sufficiently escape HTML-significant characters (angle brackets) in text nodes when converting a parsed document to Markdown via to_markdown(). While a small set of Markdown metacharacters are escaped, characters such as \u003c and \u003e are preserved, so untrusted input that is safe in to_html() — including entity-decoded text (e.g. \u0026lt;script\u0026gt;) or text from RCDATA/RAWTEXT-parsed elements like \u003ctitle\u003e, \u003ctextarea\u003e, \u003cnoscript\u003e, and \u003cplaintext\u003e — can be emitted as raw HTML in the Markdown output, enabling a sanitizer bypass and potential cross-site scripting when that output is rendered.","status":"published","published":"2026-08-23T15:33:05Z","modified":"2026-08-23T15:33:12Z","cvss":[{"version":"4.0","score":9.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"critical"},{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T15:10:52.226529Z"}],"nvd":[{"id":"CVE-2026-8445","description":"justhtml versions \u003c= 1.11.0 (fixed in 1.12.0) do not sufficiently escape HTML-significant characters (angle brackets) in text nodes when converting a parsed document to Markdown via to_markdown(). While a small set of Markdown metacharacters are escaped, characters such as \u003c and \u003e are preserved, so untrusted input that is safe in to_html() — including entity-decoded text (e.g. \u0026lt;script\u0026gt;) or text from RCDATA/RAWTEXT-parsed elements like \u003ctitle\u003e, \u003ctextarea\u003e, \u003cnoscript\u003e, and \u003cplaintext\u003e — can be emitted as raw HTML in the Markdown output, enabling a sanitizer bypass and potential cross-site scripting when that output is rendered.","status":"published","published":"2026-08-23T14:16:54.957Z","modified":"2026-10-12T16:10:00.22Z","cvss":[{"version":"4.0","score":9.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"critical","assessor":"secondary"},{"version":"3.1","score":9.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"critical","assessor":"secondary"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:12:41.018848Z"}],"osv":[{"id":"CVE-2026-8445","title":"justhtml before 1.12.0 Sanitizer Bypass via Markdown","description":"justhtml versions \u003c= 1.11.0 (fixed in 1.12.0) do not sufficiently escape HTML-significant characters (angle brackets) in text nodes when converting a parsed document to Markdown via to_markdown(). While a small set of Markdown metacharacters are escaped, characters such as \u003c and \u003e are preserved, so untrusted input that is safe in to_html() — including entity-decoded text (e.g. \u0026lt;script\u0026gt;) or text from RCDATA/RAWTEXT-parsed elements like \u003ctitle\u003e, \u003ctextarea\u003e, \u003cnoscript\u003e, and \u003cplaintext\u003e — can be emitted as raw HTML in the Markdown output, enabling a sanitizer bypass and potential cross-site scripting when that output is rendered.","status":"published","published":"2026-08-23T13:34:13.886Z","modified":"2026-10-28T02:31:02.251171577Z","cvss":[{"version":"4.0","score":9.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T15:45:18.482949Z"},{"id":"GHSA-3rcm-vjrc-p45j","title":"JustHTML has a Sanitizer Bypass (in Markdown)","description":"## 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())","status":"published","published":"2026-03-18T20:19:56Z","modified":"2026-08-24T03:56:00.500786004Z","cvss":[{"version":"4.0","score":5.3,"vector":"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","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T15:46:36.092962Z"}]}}
