CVE-2026-63670

ADVISORY - github

Summary

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

// 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.

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.

EPSS Score: 0.00236 (0.144)

Common Weakness Enumeration (CWE)

ADVISORY - nist

Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

ADVISORY - github

Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')


GitHub

CREATED

UPDATED

EXPLOITABILITY SCORE

2.8

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)

CVSS SCORE

6.1medium
PackageTypeOS NameOS VersionAffected RangesFix Versions
sanitize-htmlnpm--<=2.17.52.17.6

CVSS:3 Severity and metrics

The CVSS metrics represent different qualitative aspects of a vulnerability that impact the overall score, as defined by the CVSS Specification.

The vulnerable component is bound to the network stack, but the attack is limited at the protocol level to a logically adjacent topology. This can mean an attack must be launched from the same shared physical (e.g., Bluetooth or IEEE 802.11) or logical (e.g., local IP subnet) network, or from within a secure or otherwise limited administrative domain (e.g., MPLS, secure VPN to an administrative network zone). One example of an Adjacent attack would be an ARP (IPv4) or neighbor discovery (IPv6) flood leading to a denial of service on the local LAN segment (e.g., CVE-2013-6014).

Specialized access conditions or extenuating circumstances do not exist. An attacker can expect repeatable success when attacking the vulnerable component.

The attacker is unauthorized prior to attack, and therefore does not require any access to settings or files of the vulnerable system to carry out an attack.

Successful exploitation of this vulnerability requires a user to take some action before the vulnerability can be exploited. For example, a successful exploit may only be possible during the installation of an application by a system administrator.

An exploited vulnerability can affect resources beyond the security scope managed by the security authority of the vulnerable component. In this case, the vulnerable component and the impacted component are different and managed by different security authorities.

There is some loss of confidentiality. Access to some restricted information is obtained, but the attacker does not have control over what information is obtained, or the amount or kind of loss is limited. The information disclosure does not cause a direct, serious loss to the impacted component.

Modification of data is possible, but the attacker does not have control over the consequence of a modification, or the amount of modification is limited. The data modification does not have a direct, serious impact on the impacted component.

There is no impact to availability within the impacted component.

NIST

CREATED

UPDATED

EXPLOITABILITY SCORE

2.8

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)

CVSS SCORE

6.1medium