NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
npm · #3077 most downloaded on npm
Clean up user-submitted HTML, preserving allowlisted elements and allowlisted attributes on a per-element basis
Last release 4 days ago
30 Sep 2026
Ships unpredictably
gaps range from 3 weeks to 9 months
Nearly every release is documented
notes for 60 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
13 years old
127 releases · first in 2013
Thanks to adrbogacz for reporting the vulnerability.
logger option: pass any console-shaped object, with debug, info, warn and error methods, and sanitize-html's own diagnostics are delivered to it rather than to the console, so an application with a logging pipeline of its own can route them. Missing methods, and no option at all, fall back to the console. Those messages also lost their decorative line breaks and warning icon, so each is now a single line of text; their wording is otherwise unchanged.allowedSchemesByTag is now applied to srcset and imagesrcset URLs. Previously the per-tag lookup used the attribute name instead of the tag name, so these attributes always fell back to the global allowedSchemes and ignored a tag-specific scheme allowlist. Thanks to
spokodev for the fix.sanitize-html began escaping any markup preserved inside a disallowed iframe tag, which was a change
in behavior due to an upstream change in htmlparser2. This fix ensures such "fallback markup" is preserved without escaping, but also
fully sanitized according to the same rules as the original input. Thanks to sumitjhacodes for
the fix.When meta was allowed together with its http-equiv and content attributes, the destination URL of a <meta http-equiv="refresh" content="0;url=..."> was never checked against allowedSchemes, because it is embedded in content rather than being an attribute of its own. So javascript:, data: and other disallowed destinations passed through. The refresh URL is now extracted the way browsers do it, allowing for the different spellings, separators, quoting and letter case of url=, and checked against allowedSchemes (or allowedSchemesByTag.meta). If it is rejected, or the content cannot be parsed as a refresh, the content attribute is removed. content on other meta elements is unchanged. The default configuration does not allow meta and was not affected (CWE-79, CWE-601, GHSA-cv27-6wvh-8x7j).
Thanks to adrbogacz for reporting the vulnerability.
When noscript is listed in nonTextTags, the discarded region could end too early. Browsers with scripting enabled treat <noscript> content as raw text up to the first </noscript>, but the underlying parser treats it as markup, so an end tag for an enclosing element inside <noscript> closed it implicitly and the rest of its content was emitted as ordinary sanitized markup. The discard region now continues until the point where a browser would end the <noscript> element, while implied closes of other nonTextTags such as <option> behave as before (CWE-79, CWE-436, GHSA-x3q4-9hxx-gx8m).
Thanks to joaquiniglesiaslug for reporting the vulnerability.
The check that drops SVG animation elements (animate, animateColor, animateMotion, animateTransform, set) when they retarget a URL attribute such as href compared the full tag name, so a namespace-prefixed spelling like svg:animate was not recognized when such tags were allowed (for example with allowedTags: false). In XML serializations such as XHTML or standalone SVG, the prefixed element is a real animation element and could retarget a link to a javascript: URL after sanitization. The element and attributeName are now matched by their local names, ignoring any prefix (CWE-79, CWE-184, GHSA-374f-7chj-9948).
Thanks to Kai Aizen (SnailSploit) for reporting the vulnerability.
One column per quarter.
apostrophecms was not affected. Thanks to koyokr for responsibly disclosing the vulnerability (GHSA-g8qq-57p8-ggw5).
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).Thanks to khoadb175 for responsibly disclosing the vulnerability.
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.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).Thanks to crattack for reporting the vulnerability.
javascript: and similar. None of these are used in the default configuration of sanitize-html or apostrophe or likely to be used there, and some attributes, like an action for a form, are inherently unsafe to allow if XSS protection is your goal. Nevertheless it makes sense to block certain URL types where they are not appropriate. Some attributes are not supported at all by modern browsers but are included for completeness. Thanks to crattack for reporting the vulnerability.Nothing published for this version
Security vulnerability: the xmp tag could be used to pass forbidden markup through sanitize-html, even when xmp itself is not explicitly allowed All u…
sanitize-html and launder now share a single implementation of naughtyHref, based on that which previously existed in sanitize-html.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…
option tags. There was no vulnerability when not explicitly allowing option tags.Upgrade htmlparser2 from 8.x to 10.1.0. This improves security by correctly decoding zero-padded numeric character references (e.g., `) that previousl
htmlparser2 from 8.x to 10.1.0. This improves security by correctly decoding zero-padded numeric character references (e.g., ) that previously bypassed javascript: URL detection. Also fixes double-encoding of entities inside raw text elements like textarea and option.Fix unclosed tags (e.g., <hello) returning empty string in escape and recursiveEscape modes. Fixes #706. Thanks to Byeong Hyeon for the fix.
<hello) returning empty string in escape and recursiveEscape modes. Fixes #706.
Thanks to Byeong Hyeon for the fix.Add preserveEscapedAttributes, allowing attributes on escaped disallowed tags to be retained. Thanks to Ben Elliot for this new option.
preserveEscapedAttributes, allowing attributes on escaped disallowed tags to be retained. Thanks to Ben Elliot for this new option.Add onOpenTag and onCloseTag events to enable advanced filtering to hook into the parser. Thanks to Rimvydas Naktinis.
onOpenTag and onCloseTag events to enable advanced filtering to hook into the parser. Thanks to Rimvydas Naktinis.Allow keeping tag content when discarding with exclusive filter by returning "excludeTag". Thanks to rChaoz.
"excludeTag". Thanks to rChaoz.Fix adding text with transformTags in cases where it originally had no text child elements. Thanks to f0x.
transformTags in cases where it originally had no text child elements. Thanks to f0x.Fix to allow regex in allowedClasses wildcard whitelist. Thanks to anak-dev.
allowedClasses wildcard whitelist. Thanks to anak-dev.Documentation update regarding minimum supported TypeScript version.
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.
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 b…
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.Introduced the allowedEmptyAttributes option, enabling explicit specification of empty string values for select attributes, with the default attribute
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.
Fix to allow false in allowedClasses attributes. Thanks to Kevin Jiang for this fix!
false in allowedClasses attributes. Thanks to Kevin Jiang for this fix!.idea temp files to .gitignoreFix auto-adding escaped closing tags. In other words, do not add implied closing tags to disallowed tags when disallowedTagMode is set to any variant
disallowedTagMode is set to any variant of escape -- just escape the disallowed tags that are present. This fixes issue #464. Thanks to Daniel LiebnertagAllowed() 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.Add option parseStyleAttributes to skip style parsing. This fixes issue #547. Thanks to Bert Verhelst.
If the argument is a number, convert it to a string, for backwards compatibility. Thanks to Alexander Schranz.
Upgrades htmlparser2 to new major version ^8.0.0. Thanks to Kedar Chandrayan for this contribution.
htmlparser2 to new major version ^8.0.0. Thanks to Kedar Chandrayan for this contribution.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 sid
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.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
A denial-of-service vulnerability has been fixed by replacing global regular expression replacement logic for comment removal with a new implementatio…
Allows a more sensible set of default attributes on tags. Thanks to Zade Viggers.
<img /> tags. Thanks to Zade Viggers.Fixes style filtering to retain !important when used.
!important when used.transformTags options that was reported on issue #506. Thanks to Alex Rantos.Support for regular expressions in the allowedClasses option. Thanks to Alex Rantos.
allowedClasses option. Thanks to Alex Rantos.Fixed bug introduced by klona 2.0.5, by removing klona entirely.
Nullish HTML input now returns an empty string. Nullish value may be explicit null, undefined or implicit undefined when value is not provided. Thanks
null, undefined or implicit undefined when value is not provided. Thanks to Artem Kostiuk for the contribution.The allowedScriptHostnames and allowedScriptDomains options now implicitly purge the inline content of all script tags, not just those with src attrib
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.New allowedScriptHostnames option, it enables you to specify which hostnames are allowed in a script tag.
allowedScriptHostnames option, it enables you to specify which hostnames are allowed in a script tag.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.Added support for class names with wildcards in allowedClasses. Thanks to zhangbenber for the contribution.
allowedClasses. Thanks to zhangbenber for the contribution.Security fix: allowedSchemes and related options did not properly block schemes containing a hyphen, plus sign, period or digit, such as ms-calculator…
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.parser option, especially decodeEntities: false. See the documentation.Additional fixes for iframe validation exploits. Prevent exploits based on browsers' tolerance of the use of "\" rather than "/" and the presence of w
yarn add syntax. Thanks to Tagir Khadshiev for the contribution.Uses the standard WHATWG URL parser to stop IDNA (Internationalized Domain Name) attacks on the iframe hostname validator. Thanks to Ron Masas of Chec
Upgrades htmlparser2 to new major version ^6.0.0. Thanks to Bogdan Chadkin for the contribution.
htmlparser2 to new major version ^6.0.0. Thanks to Bogdan Chadkin for the contribution.Adds a note to the README about Typescript support (or the lack-thereof).
tel to the default allowedSchemes. Thanks to Arne Herbots for this contribution.Fixes typos and inconsistencies in the README. Thanks to Eric Lefevre-Ardant for this contribution.
Fixes a bug when using allowedClasses with an '*' wildcard selector. Thanks to Clemens Damke for this contribution.
allowedClasses with an '*' wildcard selector. Thanks to Clemens Damke for this contribution.sup added to the default allowed tags list. Thanks to Julian Lam for the contribution.
sup added to the default allowed tags list. Thanks to Julian Lam for the contribution.allowedTags README documentation. Thanks to Marco Arduini for the contribution.Updates ESLint config package and fixes warnings.
nestingLimit option added.is-plain-object package with named export. Thanks to Bogdan Chadkin for the contribution.postcss package and drop Node 11 and Node 13 support (enforced by postcss).allowedTags array was updated significantly. This mostly added HTML tags to be more comprehensive by default. You should review your projects and consider the allowedTags defaults if you are not already overriding them.Always use existing has function rather than duplicating it.
has function rather than duplicating it.Upgrade klona package. Thanks to Bogdan Chadkin for the contribution.
klona package. Thanks to Bogdan Chadkin for the contribution.Add files to package.json to prevent publishing unnecessary files to npm #392. Thanks to styfle for the contribution.
files to package.json to prevent publishing unnecessary files to npm #392. Thanks to styfle for the contribution.iframe and nl from default allowed tags. Adds most innocuous tags to the default allowedTags array.transformTags with out textFilter. Thanks to Andrzej Porebski for the help with a failing test.Prior to this patch, tag transformations which turned an attribute value into a text node could be vulnerable to code execution.
index.js file to the project root and removes all build steps within the package. Going forward, it is up to the developer to include sanitize-html in their project builds as-needed. This removes major points of conflict with project code and frees this module to not worry about myriad build-related questions.innerText. Thanks to Mike Samuel for the contribution. Prior to this patch, tag transformations which turned an attribute
value into a text node could be vulnerable to code execution.const/let variable assignment.is-plain-object to the 4.x major version.srcset to the 3.x major version.Thanks to Bogdan Chadkin for contributions to this major version update.
Updates README to include ES modules syntax.
Fixes an IE11 regression from using Array.prototype.includes, replacing it with Array.prototype.indexOf.
Array.prototype.includes, replacing it with Array.prototype.indexOf.Fixes a bug when using transformTags with out textFilter. Thanks to Andrzej Porebski for the help with a failing test.
transformTags with out textFilter. Thanks to Andrzej Porebski for the help with a failing test.Fixes CHANGELOG links. Thanks to Alex Mayer for the contribution.
srcset with parse-srcset. Thanks to Massimiliano Mirra for the contribution.Removes the unused chalk dependency.
xtend package with native Object.assign.Adds the allowedIframeDomains option. This works similar to allowedIframeHostnames, where you would set it to an array of web domains. It would then p
allowedIframeDomains option. This works similar to allowedIframeHostnames, where you would set it to an array of web domains. It would then permit any hostname on those domains to be used in iframe src attributes. Thanks to Stanislav Kravchenko for the contribution.Adds the option element to the default nonTextTagsArray of tags with contents that aren't meant to be displayed visually as text. This can be overridd
option element to the default nonTextTagsArray of tags with contents that aren't meant to be displayed visually as text. This can be overridden with the nonTextTags option.Adds a warning when style and script tags are allowed, as they are inherently vulnerable to being used in XSS attacks. That warning can be disabled by…
enforceHtmlBoundary option to process code bounded by the html tag, discarding any code outside of those tags.style and script tags are allowed, as they are inherently vulnerable to being used in XSS attacks. That warning can be disabled by including the option allowVulnerableTags: true so this choice is knowing and explicit.Fixes a bug where self-closing tags resulted in deletion with disallowedTagsMode: 'escape' set. Thanks to Thiago Negri for the contribution.
disallowedTagsMode: 'escape' set. Thanks to Thiago Negri for the contribution.abbr to the default allowedTags for better accessibility support. Thanks to Will Farrell for the contribution.mediaChildren property to the frame object in custom filters. This allows you to check for links or other parent tags that contain self-contained media to prevent collapse, regardless of whether there is also text inside. Thanks to axdg for the initial implementation and Marco Arduini for a failing test contribution.Adds eslint configuration and adds eslint to test script.
sideEffects: false on package.json to allow module bundlers like webpack tree-shake this module and all the dependencies from client build. Thanks to Egor Voronov for the contribution.tagName (HTML element name) as a second parameter passed to textFilter. Thanks to Slava for the contribution.ncreases the patch version of lodash.mergewith to enforce an audit fix.
ncreases the patch version of lodash.mergewith to enforce an audit fix.
bumped htmlparser2 dependency to the 4.x series. This fixes longstanding bugs and should cause no bc breaks for this module, since the only bc breaks
bumped htmlparser2 dependency to the 4.x series. This fixes longstanding bugs and should cause no bc breaks for this module, since the only bc breaks upstream are in regard to features we don't expose in this module.
fixed issue with bad main setting in package.json that broke 1.21.0.
fixed issue with bad main setting in package.json that broke 1.21.0.
new disallowedTagsMode option can be set to escape to escape disallowed tags rather than discarding them. Any subtags are handled as usual. If you wan
new disallowedTagsMode option can be set to escape to escape disallowed tags rather than discarding them. Any subtags are handled as usual. If you want to recursively escape them too, you can set disallowedTagsMode to recursiveEscape. Thanks to Yehonatan Zecharia for this contribution.
Your coding agent can read these notes before it upgrades. Set up the MCP server →