Skip to main content

sanitize-html CVE-2026-44990

| EUVDEUVD-2026-36566 CRITICAL
Cross-site Scripting (XSS) (CWE-79)
2026-05-14 https://github.com/apostrophecms/apostrophe GHSA-rpr9-rxv7-x643
9.3
CVSS 3.1 · Vendor: https://github.com/apostrophecms/apostrophe
Share

Severity by source

Vendor (https://github.com/apostrophecms/apostrophe) PRIMARY
9.3 CRITICAL
AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N
Red Hat
8.1 HIGH
qualitative

Primary rating from Vendor (https://github.com/apostrophecms/apostrophe).

CVSS VectorVendor: https://github.com/apostrophecms/apostrophe

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Changed
Confidentiality
High
Integrity
High
Availability
None

Lifecycle Timeline

3
Source Code Evidence Fetched
May 14, 2026 - 19:01 vuln.today
Analysis Generated
May 14, 2026 - 19:01 vuln.today
CVE Published
May 14, 2026 - 18:26 nvd
CRITICAL 9.3

Blast Radius

ecosystem impact
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 56 npm packages depend on sanitize-html (13 direct, 43 indirect)

Ecosystem-wide dependent count for version 2.17.3.

DescriptionCVE.org

Summary

Under the default configuration, sanitize-html can turn attacker-controlled content inside a disallowed xmp element into live HTML or JavaScript. This is a sanitizer bypass in the default disallowedTagsMode: 'discard' path and can lead to stored XSS in applications that render sanitized output back to users.

Details

In sanitize-html@2.17.3, the default nonTextTags list includes only script, style, textarea, and option in index.js lines 138-142. That means disallowed xmp tags are not treated as "drop the entire contents" tags.

Later, in the ontext handler at index.js lines 569-577, the code special-cases textarea and xmp and appends their text content directly to the output without escaping:

js
} else if ((options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && (tag === 'textarea' || tag === 'xmp')) {
  result += text;
}

Because htmlparser2 treats xmp as a raw-text element, markup inside xmp is parsed as text on input but becomes live markup again once it is appended unescaped to the sanitized output.

This creates a default sanitizer bypass. For example, a disallowed <xmp> wrapper can be used to smuggle <script> or event-handler payloads through sanitization.

The README also appears to contradict the implementation. In the "Discarding the entire contents of a disallowed tag" section, the documented exception list names only style, script, textarea, and option, and does not mention xmp.

PoC

Tested locally against sanitize-html@2.17.3 on Node.js v25.2.1.

  1. Install the package:
bash
npm install sanitize-html
  1. Run the following script:
js
const sanitizeHtml = require('sanitize-html');

console.log(sanitizeHtml('<xmp><script>alert(1)</script></xmp>'));
console.log(sanitizeHtml('<xmp><img src=x onerror=alert(1)></xmp>'));
console.log(sanitizeHtml('<xmp><svg><script>alert(1)</script></svg></xmp>'));
  1. Observed output:
html
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg><script>alert(1)</script></svg>
  1. Render any of the returned strings in a browser context that trusts sanitize-html output, for example:
js
const dirty = '<xmp><script>alert(1)</script></xmp>';
const clean = sanitizeHtml(dirty);

If clean is inserted into the DOM or stored and later rendered as trusted HTML, the attacker-controlled script executes.

Impact

This is a cross-site scripting vulnerability in the default sanitizer behavior. Any application that uses sanitize-html defaults and then renders the returned HTML as trusted output is impacted. A remote attacker who can submit HTML content can trigger execution of arbitrary JavaScript in another user's browser when that content is viewed.

AnalysisAI

The sanitize-html npm package allows remote attackers to bypass HTML sanitization and inject executable JavaScript by wrapping malicious payloads inside disallowed <xmp> tags, achieving stored cross-site scripting (XSS) in applications using default configurations. This affects all versions through 2.17.3, with no vendor-released patch identified at time of analysis. A publicly available proof-of-concept demonstrates the bypass, which leverages the library's special handling of raw-text elements. With a 9.3 CVSS score and network-based attack vector requiring only user interaction, this represents a critical risk for Node.js applications that render sanitized user content in browsers.

Technical ContextAI

The vulnerability stems from an incomplete implementation of HTML5 raw-text element handling in sanitize-html's parser integration with htmlparser2. The library's default nonTextTags list (index.js lines 138-142) includes only script, style, textarea, and option tags for complete content dropping, but omits the xmp element. However, the ontext handler (lines 569-577) explicitly special-cases both textarea and xmp, appending their text content directly to output without HTML entity encoding. Because htmlparser2 correctly treats xmp as a raw-text element per HTML5 parsing rules, markup inside xmp is parsed as text nodes during sanitization but becomes live executable HTML when the unescaped text is reassembled in the output string. This creates a context-switching bypass where disallowed tags like script can be smuggled through sanitization wrapped in an outer xmp container that itself gets discarded while its 'text' payload survives unescaped. The vulnerability is a classic double-parsing or content-type mismatch issue rooted in CWE-79 (Improper Neutralization of Input During Web Page Generation).

RemediationAI

No vendor-released patch identified at time of analysis for sanitize-html versions <= 2.17.3. Organizations should monitor the project's GitHub repository (https://github.com/apostrophecms/sanitize-html) and advisory GHSA-rpr9-rxv7-x643 for patch release announcements. As an immediate compensating control, add 'xmp' to the nonTextTags configuration array to force complete content dropping for xmp elements, preventing the unescaped text passthrough: sanitizeHtml(input, { nonTextTags: ['script', 'style', 'textarea', 'option', 'xmp'] }). This configuration change aligns xmp handling with the library's treatment of other raw-text elements and blocks the bypass vector. Trade-off: requires code changes to every sanitize-html invocation if using a wrapper function, or modification of application-wide sanitizer configuration. Alternative mitigation: implement Content Security Policy (CSP) with strict-dynamic and nonce-based script whitelisting in the browser layer to prevent execution of injected inline scripts even if sanitization is bypassed; however, this does not prevent DOM-based XSS via event handlers like onerror, so CSP alone is insufficient. For applications unable to patch immediately, consider replacing sanitize-html with DOMPurify (isomorphic-dompurify for Node.js) or implementing server-side rendering with context-aware output encoding.

CVE-2024-55591 CRITICAL POC
9.8 Jan 14

FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote

CVE-2014-7205 CRITICAL POC
10.0 Oct 08

Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t

CVE-2025-59528 CRITICAL POC
10.0 Sep 22

Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete

CVE-2017-14849 HIGH POC
7.5 Sep 28

Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc

CVE-2017-5941 CRITICAL POC
9.8 Feb 09

An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner

CVE-2014-3744 HIGH POC
7.5 Oct 23

Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi

CVE-2014-9566 HIGH POC
7.5 Mar 10

Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin

CVE-2013-4660 MEDIUM POC
6.8 Jun 28

The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic

CVE-2015-5688 MEDIUM POC
5.0 Sep 04

Directory traversal vulnerability in lib/app/index.js in Geddy before 13.0.8 for Node.js allows remote attackers to read

CVE-2026-45321 CRITICAL POC
9.6 May 12

Credential-harvesting malware compromised 84 versions of 42 TanStack npm packages on 2026-05-11 via chained GitHub Actio

CVE-2014-7192 CRITICAL POC
10.0 Dec 11

Eval injection vulnerability in index.js in the syntax-error package before 1.1.1 for Node.js 0.10.x, as used in IBM Rat

CVE-2013-4450 MEDIUM POC
5.0 Oct 21

The HTTP server in Node.js 0.10.x before 0.10.21 and 0.8.x before 0.8.26 allows remote attackers to cause a denial of se

Vendor StatusVendor

Share

CVE-2026-44990 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy