Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: Unstructured-IO/unstructured
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 0.24.0
Choose a base ref
...
head repository: Unstructured-IO/unstructured
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 0.24.1
Choose a head ref
  • 1 commit
  • 63 files changed
  • 3 contributors

Commits on Jul 11, 2026

  1. fix: sanitize v2 HTML output to prevent stored XSS (GHSA-v5mq-3xhg-98m9

    …) (#4394)
    
    ## 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](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
    
    - [x] PoC renders inertly — none of the four vectors execute
    - [x] `on*` attributes stripped/neutralized
    - [x] `javascript:`/`data:`/`vbscript:` schemes dropped;
    `http`/`https`/`mailto`/relative + `data:image/*` preserved
    - [x] Element text and attribute values HTML-escaped
    - [x] Attribute-value breakout impossible
    - [x] Regression tests for all four vectors + legitimate-formatting
    preservation
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    <!-- This is an auto-generated description by cubic. -->
    <a
    href="proxy.php?url=https%3A%2F%2Fgithub.com%2FUnstructured-IO%2Funstructured%2Fcompare%2F%3Ca+href%3D"https://cubic.dev/pr/Unstructured-IO/unstructured/pull/4394?utm_source=github" rel="nofollow">https://cubic.dev/pr/Unstructured-IO/unstructured/pull/4394?utm_source=github"
    target="_blank" rel="noopener noreferrer"
    data-no-image-dialog="true"><picture><source
    media="(prefers-color-scheme: dark)"
    srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
    media="(prefers-color-scheme: light)"
    srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
    alt="Review in cubic"
    src="proxy.php?url=https%3A%2F%2Fgithub.com%2FUnstructured-IO%2Funstructured%2Fcompare%2F%3Ca+href%3D"https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a" rel="nofollow">https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
    <!-- End of auto-generated description by 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>
    3 people authored Jul 11, 2026
    Configuration menu
    Copy the full SHA
    cda16b3 View commit details
    Browse the repository at this point in the history
Loading