Skip to content

Update dependency sanitize-html to v2.17.7 [SECURITY] - #85

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-sanitize-html-vulnerability
Open

Update dependency sanitize-html to v2.17.7 [SECURITY]#85
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-sanitize-html-vulnerability

Conversation

@renovate

@renovate renovate Bot commented Aug 26, 2026

Copy link
Copy Markdown

This PR contains the following updates:

Package Change Age Confidence
sanitize-html (source) 2.3.22.17.7 age confidence

sanitize-html Information Exposure vulnerability

CVE-2024-21501 / GHSA-rm97-x556-q36h

More information

Details

Versions of the package sanitize-html before 2.12.1 are vulnerable to Information Exposure when used on the backend and with the style attribute allowed, allowing enumeration of files in the system (including project dependencies). An attacker could exploit this vulnerability to gather details about the file system structure and dependencies of the targeted server.

Severity

  • CVSS Score: 5.3 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Sanitize-html Vulnerable To REDoS Attacks

CVE-2022-25887 / GHSA-cgfm-xwp7-2cvr

More information

Details

The package sanitize-html before 2.7.1 are vulnerable to Regular Expression Denial of Service (ReDoS) due to insecure global regular expression replacement logic of HTML comment removal.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


sanitize-html has incomplete URI scheme validation in that allows javascript: URIs through action, formaction, data, poster, and background attributes

CVE-2026-53606 / GHSA-vccv-cmxp-4j9h

More information

Details

Summary

sanitize-html uses allowedSchemesAppliedToAttributes (default: ['href', 'src', 'cite']) to gate the naughtyHref() function that blocks dangerous URI schemes like javascript: and vbscript:. The HTML specification defines 10+ attributes that accept URIs (action, formaction, data, poster, background, ping, xlink:href, dynsrc, lowsrc), but none of these are included in the default gate list. When a developer allows any of these attributes in their configuration, javascript: URIs pass through completely unmodified, enabling XSS.

The library has zero awareness of these URI-bearing attributes — none appear anywhere in the 854-line source file (verified by grep). No warning mechanism exists, and the README provides no security guidance about expanding allowedSchemesAppliedToAttributes when allowing form or media attributes.

Severity

Exploitation requires non-default configuration: the developer must explicitly allow a non-default tag (e.g., form) AND a non-default attribute (e.g., action). Default configuration is NOT vulnerable. However, this is a common configuration pattern for CMS platforms, form builders, and rich content editors.

Affected Versions

All versions of sanitize-html from v1.18.0 (which introduced allowedSchemesAppliedToAttributes) through at least v2.17.2. The default list has been ['href', 'src', 'cite'] since introduction and has never been expanded.

Root Cause

File: index.js:329 (sanitize-html 2.10.0, confirmed same in 2.17.x)

// Line 329 — The gate that controls scheme validation
if (options.allowedSchemesAppliedToAttributes.indexOf(a) >= 0) {
    if (naughtyHref(name, value)) {
        delete frame.attribs[a];
        return;
    }
}

Default list at line 829:

allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'],

The naughtyHref() function (lines 627-667) correctly blocks javascript:, vbscript:, and other dangerous schemes. However, it has exactly 2 call sites in the entire codebase (lines 330 and 395), both inside the indexOf gate. There is no ungated path.

When attribute name is action, formaction, data, poster, background, etc.:

  • indexOf('action') returns -1
  • The if block is skipped entirely
  • naughtyHref() is never called
  • javascript:alert(1) passes through unmodified

The escapeHtml() function at line 464 provides no defense — it only encodes & < > " characters, which are not present in javascript:alert(1).

Data Flow:

Attacker input: <form action="javascript:alert(document.cookie)">
1. htmlparser2 parses → tag='form', attribs={action:'javascript:alert(document.cookie)'}
2. index.js:298 → allowedAttributes check: 'action' in developer config → PASS
3. index.js:329 → ['href','src','cite'].indexOf('action') → -1 → SKIP naughtyHref()
4. index.js:464 → escapeHtml('javascript:alert(document.cookie)') → unchanged
5. OUTPUT: <form action="javascript:alert(document.cookie)">
Steps to Reproduce
const sanitize = require('sanitize-html');

// ===== VECTOR 1: form action (100% reliable, all modern browsers) =====
const v1 = sanitize(
    '<form action="javascript:alert(document.cookie)"><button>Submit</button></form>',
    {
        allowedTags: ['form', 'button'],
        allowedAttributes: { form: ['action'] }
    }
);
console.log('V1 (action):', v1);
// OUTPUT: <form action="javascript:alert(document.cookie)"><button>Submit</button></form>
// XSS triggers when user submits the form

// ===== VECTOR 2: button formaction (100% reliable) =====
const v2 = sanitize(
    '<button formaction="javascript:alert(1)">Click</button>',
    {
        allowedTags: ['button'],
        allowedAttributes: { button: ['formaction'] }
    }
);
console.log('V2 (formaction):', v2);
// OUTPUT: <button formaction="javascript:alert(1)">Click</button>

// ===== VECTOR 3: object data =====
const v3 = sanitize(
    '<object data="javascript:alert(1)"></object>',
    {
        allowedTags: ['object'],
        allowedAttributes: { object: ['data'] }
    }
);
console.log('V3 (data):', v3);
// OUTPUT: <object data="javascript:alert(1)"></object>

// ===== CONTROL: href IS scheme-checked (expected behavior) =====
const ctrl = sanitize(
    '<a href="javascript:alert(1)">click</a>',
    {
        allowedTags: ['a'],
        allowedAttributes: { a: ['href'] }
    }
);
console.log('Control (href):', ctrl);
// OUTPUT: <a>click</a>   ← href correctly stripped by naughtyHref()

Observed behavior: javascript: preserved on action/formaction/data but correctly stripped on href.

Expected behavior: javascript: should be stripped on ALL URI-bearing attributes, or at minimum, the library should warn developers when they allow URI-bearing attributes not covered by scheme validation.

Impact

An attacker can achieve XSS in applications that use sanitize-html with non-default configurations allowing URI-bearing attributes:

  • <form action="javascript:..."> — XSS on form submission (all modern browsers)
  • <button formaction="javascript:..."> — per-button XSS override (all modern browsers)
  • <object data="javascript:..."> — object load XSS (Chrome, Firefox)
  • <video poster="javascript:..."> — limited browser support but spec-valid

Common vulnerable configurations:

  • CMS platforms allowing form elements for user-generated content
  • Form builder applications
  • Rich text editors with extended tag allowlists
  • Email template editors allowing media/embed tags

Mitigating factors:

  • Default configuration is NOT vulnerable
  • Requires double opt-in: non-default tag + non-default attribute
  • CSP form-action directive mitigates form-based vectors
  • Developers CAN manually add attributes to allowedSchemesAppliedToAttributes
Remediation

Option 1 (Recommended): Expand the default allowedSchemesAppliedToAttributes list:

// index.js line 829, change from:
allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'],

// to:
allowedSchemesAppliedToAttributes: [
    'href', 'src', 'cite', 'action', 'formaction',
    'data', 'poster', 'background', 'ping',
    'xlink:href', 'dynsrc', 'lowsrc'
],

Option 2: Apply naughtyHref() to ALL attributes by default (invert the gate logic).

Option 3: Add a runtime warning when developers allow URI-bearing attributes not in allowedSchemesAppliedToAttributes (analogous to vulnerableTags warning for script/style at lines 124-129).

Reporter

Kevin Lee (Changseon Lee)
OPCIA Corp. / PeanutAI Inc.
Seoul, South Korea
GitHub: crattack

Severity

  • CVSS Score: 5.4 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


ApostropheCMS: Stored XSS via SVG SMIL URI-list scheme-policy bypass

CVE-2026-84371 / GHSA-g8qq-57p8-ggw5

More information

Details

Summary

When SVG animation is allowed, attributeName="href" makes values a list of URL destinations. sanitize-html accepts a list that starts with a safe fragment even when values is explicitly scheme-checked, allowing a later javascript: destination to execute when the sanitized link is activated.

Details

index.js:371-383 validates each attribute as one flat URL. It does not recognize that attributeName="href" gives the sibling values attribute SMIL URI-list semantics. For values="#safe;javascript:...", the leading fragment passes the flat check and the complete list is retained.

PoC

This was reproduced with sanitize-html@2.17.6 and Chromium 150.0.7871.124. The configuration adds SVG animation to the defaults and applies the existing scheme policy to values; it does not allow javascript:. Save this as poc.js:

const sanitize = require('sanitize-html');

const input = `<svg><a><animate attributeName="href" values="#safe;javascript:alert('XSS')" dur=".01s" fill="freeze"></animate><text y="30">Click me</text></a></svg>`;
const output = sanitize(input, {
  allowedTags: sanitize.defaults.allowedTags.concat(['svg', 'animate', 'text']),
  allowedAttributes: {
    ...sanitize.defaults.allowedAttributes,
    animate: ['attributename', 'values', 'dur', 'fill'],
    text: ['y']
  },
  allowedSchemesAppliedToAttributes:
    sanitize.defaults.allowedSchemesAppliedToAttributes.concat(['values'])
});
console.log(output);

Install and run it, then open poc.html and click Click me:

npm install sanitize-html@2.17.6
node poc.js > poc.html

The output retains the javascript: entry, and clicking the sanitized SVG displays XSS. With input changed to <a href="javascript:alert(1)">control</a>, the same configuration removes href.

Impact

In an application that accepts attacker-authored SVG animation, the attacker can store this payload without scripts or event handlers. A victim who activates the sanitized link executes JavaScript in the application's origin despite the configured scheme policy.

Suggested fix

Reject attributeName values selecting href or xlink:href on SVG animate and set, while retaining safe targets such as fill. Add values, from, and to regression cases.

Severity

  • CVSS Score: 5.4 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

apostrophecms/apostrophe (sanitize-html)

v2.17.7

Security
  • Fixed an XSS / URL scheme policy bypass affecting configurations that allow the SVG animation elements (animate, animateColor, animateMotion, animateTransform or set) together with attributeName and one of the animation value attributes. The default configuration was not affected, as these elements are not in the default allowedTags. apostrophecms was not affected. Thanks to koyokr for responsibly disclosing the vulnerability (GHSA-g8qq-57p8-ggw5).

v2.17.6

Fixes
  • Allow transformTags to emit text when textFilter is set, even if the tag is initially empty. This is consistent with the documentation. Thanks to spokodev for the fix.
Security
  • Fixed an XSS/allowlist bypass in which the contents of a raw-text element (textarea or xmp) nested inside an svg or math root were re-emitted without HTML-escaping. sanitize-html treated that content as inert raw text because htmlparser2 10.x classified raw-text elements by tag name and ignored the namespace, but a real HTML5 parser treats textarea/xmp as ordinary foreign elements inside SVG/MathML and re-parses their contents as live markup. As a result, markup and event-handler attributes that the allowlist never permitted (for example <svg><textarea><img src=x onerror=alert(1)>) could survive sanitization and execute in the browser. This is now fixed on two fronts: htmlparser2 was upgraded to 12.x, which is namespace-aware and parses textarea/xmp inside SVG/MathML as ordinary elements, so their non-allowlisted children (such as the injected img) are dropped by the allowlist instead of being preserved as raw text; and any raw-text content sanitize-html still emits for these tags (at HTML integration points such as foreignObject/mtext, or outside foreign content) is always HTML-escaped. The default configuration is not affected; the precondition is an allowedTags that includes svg or math together with textarea or xmp. Thanks to khoadb175 for responsibly disclosing the vulnerability.
  • Fixed a mutation-XSS / allowedTags bypass affecting configurations that allow the textarea or xmp raw-text tags. htmlparser2 10.x did not recognize an end tag with a trailing solidus (e.g. </textarea/>) as closing the element, so it kept the following markup as raw text, but a spec-compliant browser treats </textarea/> as a valid close and parses that markup as a live element. Because raw-text content was re-emitted without escaping, a payload such as <textarea></textarea/><img src=x onerror=...> could smuggle non-allowlisted, executable markup through the sanitizer. The default configuration was not affected. This is now defended at two layers: htmlparser2 was upgraded to 12.x, whose tokenizer closes these end tags correctly, and the raw text sanitize-html emits for these tags is always escaped so no < can reopen a tag when the output is re-parsed (textarea, an RCDATA element whose entities htmlparser2 decodes, is escaped like normal text, while xmp, a raw-text element, has only its angle brackets escaped to avoid double-encoding already-encoded entities). Because htmlparser2 is ESM-only from version 11 onward, sanitize-html now requires Node.js >=22.12.0 (the first 22.x release in which require() of an ES module is available unflagged). Thanks to bibu123456 for reporting the vulnerability and Kayiz-PT for coordinating the disclosure (GHSA-jxwj-j7wr-gfrw).

v2.17.5

Compare Source

v2.17.4

Compare Source

Changes
  • sanitize-html and launder now share a single implementation of naughtyHref, based on that which previously existed in sanitize-html.
Security
  • Security vulnerability: the xmp tag could be used to pass forbidden markup through sanitize-html, even when xmp itself is not explicitly allowed All users of sanitize-html should update immediately. Thanks to Vincenzo Turturro for reporting the vulnerability.

v2.17.3

Compare Source

Security
  • Fix vulnerability introduced in version 2.17.2 that allowed XSS attacks if the developer chose to permit option tags. There was no vulnerability when not explicitly allowing option tags.

v2.17.2

Compare Source

Changes
  • Upgrade htmlparser2 from 8.x to 10.1.0. This improves security by correctly decoding zero-padded numeric character references (e.g., &#&#8203;0000001) that previously bypassed javascript: URL detection. Also fixes double-encoding of entities inside raw text elements like textarea and option.

v2.17.1

Compare Source

Fixes
  • Fix unclosed tags (e.g., <hello) returning empty string in escape and recursiveEscape modes. Fixes #​706.
    Thanks to Byeong Hyeon for the fix.

v2.17.0

Compare Source

  • Add preserveEscapedAttributes, allowing attributes on escaped disallowed tags to be retained. Thanks to Ben Elliot for this new option.

v2.16.0

Compare Source

  • Add onOpenTag and onCloseTag events to enable advanced filtering to hook into the parser. Thanks to Rimvydas Naktinis.

v2.15.0

Compare Source

  • Allow keeping tag content when discarding with exclusive filter by returning "excludeTag". Thanks to rChaoz.

v2.14.0

Compare Source

  • Fix adding text with transformTags in cases where it originally had no text child elements. Thanks to f0x.

v2.13.1

Compare Source

  • Fix to allow regex in allowedClasses wildcard whitelist. Thanks to anak-dev.

v2.13.0

Compare Source

  • Documentation update regarding minimum supported TypeScript version.

  • Added disallowedTagsMode: completelyDiscard option to remove the content also in HTML. Thanks to Gauav Kumar for this addition.

v2.12.1

Compare Source

  • Do not parse sourcemaps in post-css. This fixes a vulnerability in which information about the existence or non-existence of files on a server could be disclosed via properly crafted HTML input when the style attribute is allowed by the configuration. Thanks to the Snyk Security team for the disclosure and to Dylan Armstrong for the fix.

v2.12.0

Compare Source

  • Introduced the allowedEmptyAttributes option, enabling explicit specification of empty string values for select attributes, with the default attribute set to alt. Thanks to Na for the contribution.

  • Clarified the use of SVGs with a new test and changes to documentation. Thanks to Gauav Kumar for the contribution.

  • Do not process source maps when processing style tags with PostCSS.

v2.11.0

Compare Source

  • Fix to allow false in allowedClasses attributes. Thanks to Kevin Jiang for this fix!
  • Upgrade mocha version
  • Apply small linter fixes in tests
  • Add .idea temp files to .gitignore
  • Thanks to Vitalii Shpital for the updates!
  • Show parseStyleAttributes warning in browser only. Thanks to mog422 for this update!
  • Remove empty non-boolean attributes via an exhaustive, configurable list of known non-boolean attributes. Thanks to Dylan Armstrong for this update!

v2.10.0

Compare Source

  • Fix auto-adding escaped closing tags. In other words, do not add implied closing tags to disallowed tags when disallowedTagMode is set to any variant of escape -- just escape the disallowed tags that are present. This fixes issue #​464. Thanks to Daniel Liebner
  • Add tagAllowed() helper function which takes a tag name and checks it against options.allowedTags and returns true if the tag is allowed and false if it is not.

v2.9.0

Compare Source

v2.8.1

Compare Source

  • If the argument is a number, convert it to a string, for backwards compatibility. Thanks to Alexander Schranz.

v2.8.0

Compare Source

  • Upgrades htmlparser2 to new major version ^8.0.0. Thanks to Kedar Chandrayan for this contribution.

v2.7.3

Compare Source

  • If allowedTags is falsy but not exactly false, then do not assume that all tags are allowed. Rather, allow no tags in this case, to be on the safe side. This matches the existing documentation and fixes issue #​176. Thanks to Kedar Chandrayan for the fix.

v2.7.2

Compare Source

  • Closing tags must agree with opening tags. This fixes issue #​549, in which closing tags not associated with any permitted opening tag could be passed through. No known exploit exists, but it's better not to permit this. Thanks to
    Kedar Chandrayan for the report and the fix.

v2.7.1

Compare Source

  • Protocol-relative URLs are properly supported for script tags. Thanks to paweljq.
  • A denial-of-service vulnerability has been fixed by replacing global regular expression replacement logic for comment removal with a new implementation. Thanks to Nariyoshi Chida of NTT Security Japan for pointing out the issue.

v2.7.0

Compare Source

  • Allows a more sensible set of default attributes on <img /> tags. Thanks to Zade Viggers.

v2.6.1

Compare Source

  • Fixes style filtering to retain !important when used.
  • Fixed trailing text bug on transformTags options that was reported on issue #​506. Thanks to Alex Rantos.

v2.6.0

Compare Source

  • Support for regular expressions in the allowedClasses option. Thanks to Alex Rantos.

v2.5.3

Compare Source

  • Fixed bug introduced by klona 2.0.5, by removing klona entirely.

v2.5.2

Compare Source

  • Nullish HTML input now returns an empty string. Nullish value may be explicit null, undefined or implicit undefined when value is not provided. Thanks to Artem Kostiuk for the contribution.
  • Documented that all text content is escaped. Thanks to Siddharth Singh.

v2.5.1

Compare Source

  • The allowedScriptHostnames and allowedScriptDomains options now implicitly purge the inline content of all script tags, not just those with src attributes. This behavior was already strongly implied by the fact that they purged it in the case where a src attribute was actually present, and is necessary for the feature to provide any real security. Thanks to Grigorii Duca for pointing out the issue.

v2.5.0

Compare Source

  • New allowedScriptHostnames option, it enables you to specify which hostnames are allowed in a script tag.
  • New allowedScriptDomains option, it enables you to specify which domains are allowed in a script tag. Thank you to Yorick Girard for this and the allowedScriptHostnames contribution.
  • Updates whitelist to allowlist.

v2.4.0

Compare Source

  • Added support for class names with wildcards in allowedClasses. Thanks to zhangbenber for the contribution.

v2.3.3

Compare Source

  • Security fix: allowedSchemes and related options did not properly block schemes containing a hyphen, plus sign, period or digit, such as ms-calculator:. Thanks to Lukas Euler for pointing out the issue.
  • Added a security note about the known risks associated with using the parser option, especially decodeEntities: false. See the documentation.

Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate
renovate Bot force-pushed the renovate/npm-sanitize-html-vulnerability branch from b9d4715 to 7303b80 Compare September 2, 2026 02:02
@renovate renovate Bot changed the title Update dependency sanitize-html to v2.17.5 [SECURITY] Update dependency sanitize-html to v2.17.7 [SECURITY] Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants