GHSA-jxwj-j7wr-gfrw: XSS

Published Sep 3, 2026
·
Updated

Summary A mutation-XSS / allowedTags bypass: when textarea (or xmp) is included in allowedTags, an input containing a literal </textarea/> (a solidus right after the RCDATA end-tag name) lets non-allowed markup such as <img src=x onerror=…> pass through sanitizeHtml() live and unescaped, even though img/onerror are not in the allowlist. A spec-compliant browser executes the surviving handler — XSS. This is a literal-solidus variant that bypasses the two most recent fixes in this code area (CVE-2026-40186, CVE-2026-44990), both already applied in 2.17.5. The default configuration is not affected.

Details sanitize-html emits the text content of HTML raw-text elements (textarea, xmp) without escaping. Two things combine: - Parser differential: on input, htmlparser2 does NOT recognize </textarea/> (solidus after the RCDATA end-tag name) as a close tag; it emits </textarea/><img …> as a single raw-text node. - Unescaped passthrough: the ontext handler (index.js ~575-583) appends textarea/xmp content with result += text (no escapeHtml), assuming it is "already properly encoded" — true for entity-decoded content (what CVE-2026-40186 fixed) but false for this mis-tokenized literal close tag. A spec browser treats </textarea/> as a valid textarea close, so the following <img onerror> is parsed as a live element. The recent fixes addressed entity-encoding (CVE-2026-40186) and the xmp default (CVE-2026-44990); neither covers the literal-solidus mis-tokenization, so the raw passthrough still leaks.

PoC js // npm i sanitize-html@2.17.5 parse5 && node poc.js const sanitizeHtml = require('sanitize-html'); const input = '<textarea></textarea/><img src=x onerror="alert(document.domain)">'; const opts = { allowedTags: sanitizeHtml.defaults.allowedTags.concat(['textarea']) }; // img NOT allowed console.log(sanitizeHtml(input, opts)); // => <textarea></textarea/><img src=x onerror="alert(document.domain)"></textarea> // the <img onerror> survives live and unescaped

console.log(sanitizeHtml(input)); // default config (no textarea allowed) => "" (safe) Re-parsing the sanitized OUTPUT with parse5 (the WHATWG HTML parser browsers/jsdom use) yields a live <img src=x onerror=alert(document.domain)> at body level (it escaped the textarea RCDATA, not inert text) → the onerror fires in a browser. Confirmed on 2.17.5 (Node v24). A canonical poc.js is attached. <img width="650" height="118" alt="image" src="https://github.com/user-attachments/assets/d63bb5b7-ba3e-4b0e-a821-86a453ea0352" />

Impact Cross-site scripting (CWE-79). Requires textarea (or xmp) in allowedTags — a benign-looking, common addition in form builders, CMS, and rich-text editors. Adding a harmless tag that then enables XSS via non-allowed img/onerror breaks the sanitizer's core contract; the maintainers have fixed this class before (e.g. GHSA-9mrh). An attacker who can submit content rendered through such a configuration achieves stored/reflected XSS (cookie theft, session hijack). Severity Medium (default config is safe; user interaction to view the page). Suggested fix: route textarea/xmp content through escapeHtml instead of the raw passthrough, and/or fix the htmlparser2 </tag/> RCDATA end-tag tokenization to match the WHATWG spec.

Affected Software

1 affected componentFixes available
npm/sanitize-html<=2.17.5
2.17.6

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/sanitize-html to a version that resolves this vulnerability.

    Fixed in 2.17.6
  2. Upgrade

    Upgrade sanitize-html to a version that resolves this vulnerability.

    Fixed in 2.17.5Patch GHSA-9mrh
  3. Configuration

    Do not include 'textarea' or 'xmp' in sanitize-html's allowedTags. The described mutation-XSS/allowedTags bypass occurs when 'textarea' (or 'xmp') is included in allowedTags; the default configuration is safe.

    sanitize-html (options) allowedTags = exclude 'textarea' and 'xmp' unless strictly required
  4. Configuration

    Modify sanitize-html so that textarea/xmp content is rendered via escapeHtml (instead of raw passthrough via the ontext handler), preventing live/unescaped content from surviving as markup after sanitization.

    sanitize-html (sanitization pipeline) escapeHtml handling for raw text passthrough = route textarea/xmp content through escapeHtml instead of raw passthrough
  5. Configuration

    Fix htmlparser2 tokenization for literal-solidus variants in RCDATA end tags (e.g., '</textarea/>' / '</xmp/>') so the parser matches WHATWG spec and does not treat '</textarea/>' as a valid close that allows subsequent markup to be parsed as live HTML.

    htmlparser2 RCDATA end-tag tokenization = fix '</tag/>' handling to match WHATWG spec (literal-solidus mis-tokenization)
  6. Compensating control

    If you re-render sanitized output in a browser, re-parse the sanitized HTML using a WHATWG HTML parser (as done with parse5) in tests/QA to ensure the output does not contain re-parseable live markup from literal-solidus RCDATA variants.

Event History

Sep 3, 2026
Advisory Published
via GitHub·08:07 PM
Data Sourced
via GitHub·08:07 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which deployments are exposed?

Deployments are exposed when sanitize-html is configured to allow textarea or xmp and sanitizes attacker-controlled HTML. The default configuration is not affected.

2

What must an attacker supply to trigger the issue?

The attacker needs to submit HTML containing a literal </textarea/> or equivalent xmp construct followed by markup that is not allowed, such as an image element with an event handler. No authentication is required, but a victim must load the resulting sanitized content in a spec-compliant browser.

3

How can I determine whether my configuration is vulnerable?

Review the allowedTags configuration for textarea or xmp. If either tag is allowed, test whether sanitizing content with a literal solidus after its closing tag name leaves subsequent disallowed markup unescaped in the output.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203