CVE-2026-44990 - sanitize-html xmp raw-text XSS

CVE-2026-44990 / GHSA-rpr9-rxv7-x643 is a critical stored-XSS sanitizer bypass in sanitize-html 2.17.3. In the default disallowedTagsMode value of discard, xmp raw-text content can be emitted as trusted HTML instead of being dropped with the disallowed tag. Applications that sanitize attacker-controlled or mixed-trust HTML and then store or render the returned string to other users can expose sessions, CSRF tokens, privileged admin actions, tenant data, or account takeover flows.

The narrow package fix is an upgrade to sanitize-html 2.17.4+, but a mergeable remediation should also verify the whole sanitizer-to-sink path. A repository can still be exposed if a stale bundle, lockfile, CMS plugin, rich text preview, email renderer, or post-sanitization transform continues to trust output from 2.17.3.

When to use it

Use this recipe when a JavaScript/TypeScript application sanitizes, stores, or renders mixed-trust HTML with sanitize-html. It is built for source-code remediation, stored-XSS review, sanitizer-to-sink auditing, and evidence that disallowed raw-text content cannot re-enter trusted rendering paths.

Inputs

  • sanitize-html version, package manifests, lockfiles, sanitizer configuration, render sinks, CSP/session posture, and affected content types.
  • Source paths that accept HTML, sanitize it, store it, transform it, render it, or mark it safe in templates/frameworks.
  • Regression fixtures for xmp raw-text payloads, benign HTML, nested disallowed tags, stored content replay, and expected inert output.
  • Evidence of who can submit HTML, who can view stored output, privileged pages that render it, and any token/session exposure.

Affected versions

Package Vulnerable versions Fixed versions
sanitize-html 2.17.3 2.17.4+

OSV and the reviewed GitHub advisory both identify 2.17.3 as the affected version and 2.17.4 as the fixed version. NVD had not yet ingested CVE-2026-44990 when this recipe was written on 2026-06-11, so prefer the GitHub-reviewed advisory, OSV record, and upstream fix commit for version facts until NVD catches up.

Indicator-of-exposure

  • The repository resolves sanitize-html 2.17.3 in a manifest, lockfile, SBOM, Docker layer, generated dependency report, vendored bundle, CMS plugin, or static asset package.
  • Untrusted or mixed-trust HTML reaches sanitizeHtml(...), an application wrapper around it, or a framework/CMS helper that delegates to sanitize-html.
  • Sanitized output is inserted with innerHTML, dangerouslySetInnerHTML, server-side templates, HTML email templates, markdown/rich-text previews, CMS block rendering, support/admin review panes, or equivalent string-to-DOM sinks.
  • The application uses default sanitize-html behavior or custom nonTextTags/disallowedTagsMode settings that do not explicitly drop raw-text tag contents.
  • Sanitized content can be viewed by privileged users, other tenants, support staff, reviewers, moderators, or admins.

Quick checks:

rg -n "sanitize-html|sanitizeHtml|sanitize\\(|disallowedTagsMode|nonTextTags|xmp|innerHTML|dangerouslySetInnerHTML|rich text|wysiwyg|markdown|preview|email|template" .
npm ls sanitize-html
pnpm why sanitize-html
yarn why sanitize-html

Windows:

rg -n "sanitize-html|sanitizeHtml|sanitize\(|disallowedTagsMode|nonTextTags|xmp|innerHTML|dangerouslySetInnerHTML|rich text|wysiwyg|markdown|preview|email|template" .
npm ls sanitize-html
pnpm why sanitize-html
yarn why sanitize-html

Do not validate exposure by executing live XSS payloads in a real browser session. Use local, inert fixtures and assertions against sanitized output and render helpers.

Remediation strategy

  • Upgrade every controlled sanitize-html resolution to 2.17.4+.
  • Refresh package-manager lockfiles, SBOMs, bundles, Docker layers, dependency reports, deployment manifests, and static CMS/editor assets that pin or embed sanitize-html.
  • Audit custom sanitizer options. If the repository overrides nonTextTags, ensure the complete drop-content list includes raw-text elements that should never be re-emitted as trusted markup.
  • Add safe regression fixtures that pass raw-text tag input through the repository’s real sanitizer wrapper and then through the real render helper. The tests should assert dangerous-looking child markup is removed or encoded without executing scripts.
  • Review post-sanitization transforms. Avoid serializing, reparsing, mutating, concatenating, or markdown-processing sanitized HTML in a way that creates a second, less-protected trust boundary.
  • If upgrade cannot deploy immediately, temporarily render high-risk user-supplied rich text as plain text, disable affected preview/export surfaces, or block stored HTML submission for untrusted users until fixed.

The prompt

Model context: this prompt was generated by GPT 5.5 Extra High reasoning.

You are remediating CVE-2026-44990 / GHSA-rpr9-rxv7-x643, a critical
`sanitize-html 2.17.3` default sanitizer bypass involving `xmp` raw-text
handling. Produce exactly one output:

- A reviewer-ready PR/change request that upgrades `sanitize-html`, refreshes
  generated artifacts, audits sanitizer options and render sinks, adds safe
  regression fixtures, and documents operator actions, or
- TRIAGE.md if this repository does not control an affected `sanitize-html`
  dependency, wrapper, bundle, CMS/editor asset, or rich-text rendering path.

## Rules

- Scope only CVE-2026-44990 / GHSA-rpr9-rxv7-x643 and directly related
  `sanitize-html` dependency resolution, sanitizer configuration, stored HTML,
  and sanitizer-to-sink render paths.
- Treat sessions, cookies, JWTs, CSRF tokens, tenant data, support content,
  admin pages, moderation queues, customer messages, email content, and model
  output as sensitive.
- Do not execute live XSS payloads in a browser with real credentials,
  customer data, privileged accounts, or production content.
- Do not remove sanitization, disable escaping, allow all HTML, or replace
  sanitizer checks with broad string filters to make tests pass.
- Do not auto-merge.

## Steps

1. Inventory every `sanitize-html` reference controlled by this repository:
   package manifests, lockfiles, workspaces, transitive dependency reports,
   SBOMs, Dockerfiles, image layers, vendored bundles, CMS/editor packages,
   static assets, server templates, frontend render helpers, and tests.
2. Determine every resolved `sanitize-html` version. A target is vulnerable if
   it resolves to exactly `2.17.3`.
3. Find sanitizer entry points: direct `sanitizeHtml(...)` calls, internal
   wrappers, framework filters, CMS rich-text pipelines, markdown-to-HTML
   pipelines, email renderers, import/export jobs, admin preview panes, and
   stored-content viewers.
4. Trace each sanitizer-to-sink path. Identify whether sanitized output reaches
   `innerHTML`, `dangerouslySetInnerHTML`, server templates, HTML email,
   rich-text previews, exported HTML, cached rendered content, or a
   post-processing transform that reparses HTML.
5. Review sanitizer options. Flag custom `nonTextTags`, `allowedTags`,
   `allowedAttributes`, `transformTags`, `exclusiveFilter`,
   `disallowedTagsMode`, and wrapper defaults that may keep raw-text tag
   contents or reintroduce unsafe markup after sanitization.
6. If the repository does not control an affected dependency or rendering
   path, stop with `TRIAGE.md` listing checked files, resolved versions,
   runtime owner, and why this code is not exposed.
7. Upgrade all controlled resolutions to `sanitize-html 2.17.4+`. Regenerate
   package locks, dependency metadata, SBOMs, bundles, container layers,
   deployment render output, and generated dependency reports.
8. If this repository customizes `nonTextTags`, update the full option value
   so raw-text elements that should be dropped are still dropped. Do not rely
   on partial option merging unless tests prove the final runtime options.
9. Add safe regression coverage using inert local fixtures:
   - the repository sanitizer wrapper does not emit child markup from
     disallowed raw-text tags as trusted HTML;
   - custom `nonTextTags` and `disallowedTagsMode` settings fail closed;
   - sanitized stored content remains inert when rendered through the real
     sink helper;
   - privileged/admin/support views use the same fixed sanitizer or a stricter
     server-side policy;
   - dependency policy rejects `sanitize-html 2.17.3`.
10. Review existing stored content and caches where this repository owns them.
    If user-submitted HTML may have been sanitized by `2.17.3`, document
    whether content needs re-sanitization, cache purge, or moderation review.
11. Add a PR body section named `CVE-2026-44990 operator actions` that states:
    - `sanitize-html` versions before and after;
    - each rich-text, preview, import/export, email, admin, or support render
      path reviewed;
    - whether stored content or cached rendered HTML needs re-sanitization;
    - whether privileged viewers or cross-tenant surfaces were exposed;
    - any temporary plain-text rendering or submission restrictions;
    - validation commands that passed.
12. Run available validation: package install, lockfile consistency,
    sanitizer unit tests, frontend/server render tests, rich-text fixtures,
    lint/typecheck, build, dependency/security scan, SBOM refresh, and
    deployment render checks.
13. Use PR title:
    `fix(sec): remediate CVE-2026-44990 in sanitize-html rendering`

## Stop conditions

- No controlled manifest, lockfile, bundle, image, SBOM, or generated report
  resolves `sanitize-html 2.17.3`.
- The repository only references `sanitize-html` in documentation, examples,
  test-only fixtures, or non-executed code.
- The affected sanitizer is owned by a hosted CMS, plugin vendor, or platform
  team outside this repository; name the owner and required fixed version.
- Meaningful verification would require live XSS execution, real browser
  sessions, customer content, tenant data, privileged accounts, or production
  credentials.
- Validation fails for unrelated pre-existing reasons; document those failures
  instead of broadening scope.

Output contract

  • A reviewer-ready PR or change request that upgrades sanitize-html, audits sanitizer-to-sink paths, adds inert regression fixtures, and documents any stored-content cleanup or re-sanitization.
  • Or a TRIAGE.md file that lists inspected files, owner, observed version, render sinks, submitter/viewer boundary, required fix, and residual risk.
  • The output must include exact validation commands and must not include live XSS payloads against production, real session tokens, or customer content.

Verification - what the reviewer looks for

  • No controlled dependency graph, lockfile, bundle, image, SBOM, or generated dependency report resolves sanitize-html 2.17.3.
  • Tests exercise the real sanitizer wrapper and the real render sink, not only a direct package call.
  • Custom nonTextTags, disallowedTagsMode, and rich-text options are covered by tests and fail closed for raw-text tag contents.
  • Stored-content caches, previews, admin/support viewers, and email/export renderers are either fixed, temporarily disabled, or explicitly triaged.
  • Operator notes call out whether previously stored HTML needs re-sanitization or cache purging.

Watch for

  • Updating package.json while lockfiles, Docker layers, SBOMs, static bundles, CMS plugin packages, or generated dependency reports still contain 2.17.3.
  • Assuming this is only an Apostrophe CMS issue. The vulnerable package is sanitize-html, so any direct or transitive default use can matter.
  • Custom nonTextTags values that replace the package default and accidentally omit raw-text elements that should be dropped.
  • Tests that compare sanitized strings without exercising the actual server-side or frontend render helper.
  • Fixing public content pages while privileged admin, support, moderation, preview, export, or email rendering surfaces still trust stale sanitized HTML.
  • Logging full customer HTML, cookies, tokens, tenant identifiers, or admin page content while investigating stored XSS exposure.

References