-
fix: sanitize v2 HTML output to prevent stored XSS (GHSA-v5mq-3xhg-98m9) (#4394)
发布于
2026-07-11 16:24:32 +00:00 Summary
The v2 (ontology) HTML path emitted untrusted document markup with no
output encoding, so attacker-controlled content in a parsed document
survived intoelements_to_html()/metadata.text_as_htmland
executed when the output was viewed in a browser (stored XSS,
GHSA-v5mq-3xhg-98m9).
All four reported vectors —<img onerror>,<svg onload>
(attribute-value breakout),<a href="javascript:">, andon*handlers
— are now neutralized.The fix layers output-encoding at the emitter, filtering at ingest, and
a sanitizer sweep at the assembly boundary.Changes
- New
unstructured/documents/html_sanitization.py— single source
of truth for the policy: tag allowlist, attribute allowlist (drops all
on*handlers), URL-scheme filter (is_safe_url) rejecting
javascript:/vbscript:/non-imagedata:while preserving
http/https/mailto/tel/relative anddata:image/*, plus an
nh3-backedsanitize_html_fragment. ontology.py(OntologyElement.to_html) — root-cause fix:
HTML-escape element text and attribute values (quote=Truecloses the
attribute-value breakout), drop unsafe attributes, and validate the tag
name against the allowlist (non-allowlisted tags like<script>fall
back to inert<span>). This makestext_as_htmlsafe on its own.
to_textnow strips markup from the raw text rather than the
newly-escaped HTML, preserving text extraction.transformations.py— attribute handling at ingest now filters
(dropson*/unsafe schemes) instead of escaping, so escaping happens
exactly once at emit (no double-encoding). Backwards-compatible alias
retained.convert.py(elements_to_html) — runs assembled output through
nh3as defense-in-depth, covering attributes injected outside the
emitter (e.g.hreffrommetadata.url). Also fixes a node-skipping
bug when reinserting sanitized content.- Adds
nh3dependency; version bump to0.24.1with CHANGELOG entry.
Tests
- New
test_html_sanitization.py(unit) andtest_xss_sanitization.py
(end-to-end PoC from the advisory), asserting all four vectors render
inert in bothelements_to_htmloutput andtext_as_html, plus
preservation of tables, headings, safe links, and base64 images. - Updated two existing ontology tests whose expectations encoded the
pre-fix behavior — notablytest_malformed_html, which previously
asserted a live<script>tag in the output.
Acceptance criteria
- PoC renders inertly — none of the four vectors execute
on*attributes stripped/neutralizedjavascript:/data:/vbscript:schemes dropped;
http/https/mailto/relative +data:image/*preserved- Element text and attribute values HTML-escaped
- Attribute-value breakout impossible
- Regression tests for all four vectors + legitimate-formatting
preservation
🤖 Generated with Claude Code
Co-authored-by: Claude Opus 4.8 (1M context) noreply@anthropic.com
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>下载附件
- New