发布

  • fix: sanitize v2 HTML output to prevent stored XSS (GHSA-v5mq-3xhg-98m9) (#4394)

    frostbyte_neo 发布于 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 into elements_to_html() / metadata.text_as_html and
    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:">, and on* 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-image data: while preserving
      http/https/mailto/tel/relative and data:image/*, plus an
      nh3-backed sanitize_html_fragment.
    • ontology.py (OntologyElement.to_html) — root-cause fix:
      HTML-escape element text and attribute values (quote=True closes 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 makes text_as_html safe on its own.
      to_text now strips markup from the raw text rather than the
      newly-escaped HTML, preserving text extraction.
    • transformations.py — attribute handling at ingest now filters
      (drops on*/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
      nh3 as defense-in-depth, covering attributes injected outside the
      emitter (e.g. href from metadata.url). Also fixes a node-skipping
      bug when reinserting sanitized content.
    • Adds nh3 dependency; version bump to 0.24.1 with CHANGELOG entry.

    Tests

    • New test_html_sanitization.py (unit) and test_xss_sanitization.py
      (end-to-end PoC from the advisory), asserting all four vectors render
      inert in both elements_to_html output and text_as_html, plus
      preservation of tables, headings, safe links, and base64 images.
    • Updated two existing ontology tests whose expectations encoded the
      pre-fix behavior — notably test_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/neutralized
    • javascript:/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

    Review in cubic


    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>

    下载附件