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-htmlversion, 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
xmpraw-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.3in 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 tosanitize-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-htmlbehavior or customnonTextTags/disallowedTagsModesettings 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-htmlresolution to2.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.mdfile 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.jsonwhile lockfiles, Docker layers, SBOMs, static bundles, CMS plugin packages, or generated dependency reports still contain2.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
nonTextTagsvalues 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.
Related recipes
- Source code injection sink audit
- Source code secrets and data exposure audit
- OWASP Top 10 2026 audit
- SAST finding triage and fix
References
- GitHub Advisory Database: https://github.com/advisories/GHSA-rpr9-rxv7-x643
- OSV: https://osv.dev/vulnerability/GHSA-rpr9-rxv7-x643
- Upstream validation issue: https://github.com/apostrophecms/apostrophe/issues/5418
- Upstream fix commit: https://github.com/apostrophecms/apostrophe/commit/8d4c882b4ed3a7ce802cd87f89f0c1cb7482b8c2