mrkeyoor.com_
Sun 20 Sept 17:54 UTC
npmSecurityupdated 20 Sept 2026

sanitize-html review

sanitize-html 2.17.7 accepts an HTML fragment, parses it with htmlparser2, and writes a new fragment that follows your allowlist for elements, attributes, URL schemes, classes, inline styles, and embeds. Rejected tags usually lose their wrappers while safe text remains, and output text and attribute values are escaped. This makes it a Node-side boundary for rich-text editor or pasted HTML, not a general browser security layer. Version 2.17.7 fixes a URL-scheme bypass that required opting into SVG animation elements plus `attributeName` and animation value attributes; its maintained defaults did not expose that route. Our browser build measured 164.1 KB minified and 63 KB gzipped, and the npm package has no TypeScript declarations.

Verdict

sanitize-html 2.17.7 installed in 1.1 seconds with 20 packages and 5 MB in our sandbox, and npm audit found 0 known vulnerabilities. Use it for a tested server-side rich-text allowlist; choose DOMPurify for browser-first parsing and avoid enabling SVG or raw-text states without dedicated mutation-XSS tests.

We installed it

Lab card: what happened when we installed sanitize-htmlScreenshot of sanitize-html documentation
Install✓ · 1.1s20 packages on disk · 5 MB
ImportESM import works · require() works · CommonJS package
Browser63 KBgzipped (164.1 KB minified), bundled with esbuild
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does sanitize-html install cleanly?

Yes. In a fresh container with an empty cache, npm install sanitize-html finished in 1 seconds, leaving 20 packages and 5 MB on disk. npm audit reported no known vulnerabilities.

How much does sanitize-html add to a browser bundle?

63 KB gzipped (164.1 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does sanitize-html work with both ESM and CommonJS?

Yes. Both import 'sanitize-html' and require('sanitize-html') worked in Node 22 in our run. The package is published as CommonJS.

Does sanitize-html include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

sanitize-html or dompurify: which should you use?

dompurify: Use it for browser DOM sanitization, or on a server where jsdom is already an accepted dependency. sanitize-html 2.17.7 installed in 1.1 seconds with 20 packages and 5 MB in our sandbox, and npm audit found 0 known vulnerabilities.

When should you not use sanitize-html?

The only execution point is an untrusted browser. Server code cannot rely on client-side cleaning, and DOMPurify matches browser DOM parsing more closely for previews.

API stability4/5The package has kept one `sanitizeHtml(input, options)` entry and familiar controls for tags, attributes, schemes, styles, classes, transforms, and filters throughout 2.x. Operational compatibility has still moved: 2.17.6 upgraded to ESM-only htmlparser2 12 and raised the Node requirement to 22.12.0, while security patches changed output for several unusual parser states. The policy object is stable, but its exact edge-case output needs regression tests.
Docs3/5The monorepo README explains defaults, tag modes, value-restricted attributes, CSS regex rules, iframes, transforms, filters, parser options, browser use, and separate TypeScript types with runnable examples. One important statement is stale: its requirements section still says Node 10 or later, while the published 2.17.7 metadata requires Node 22.12.0. Security cautions also sit inside one long reference page, so a quick setup can miss interacting options.
Maintenance4/5Release 2.17.7 shipped on August 13, 2026 with a named advisory fix, and the active Apostrophe monorepo was pushed on August 25. Other 2026 releases addressed raw-text reparsing, SVG and MathML namespaces, `option`, `xmp`, unsafe URL attributes, and nonstandard `nonTextTags`. GitHub's 135 open issues and pull requests and 4,607 stars cover the whole monorepo, not this package alone, so they are only broad maintenance signals.
Ecosystem5/5npm counted 10,536,119 downloads in the latest completed week. The package fits Node CMS, comment, Markdown, email, and editor ingestion paths, and its policy supports tags, attributes, schemes, CSS classes, style properties, iframes, tag transforms, and filters. Community declarations exist as `@types/sanitize-html`. Browser applications also have DOMPurify, while unified pipelines can stay in HAST through `hast-util-sanitize`.

Use it if

  • A Node service accepts rich-text HTML and must enforce the same narrow policy before storing or rendering it.
  • Links, iframes, classes, and inline styles need separate allowlists rather than one blanket list of attributes.
  • Pasted editor markup should lose unwanted wrappers and CSS while keeping readable text and selected formatting.
  • The sanitizer must rewrite tags or links and remove empty elements during the same parsing pass.
Skip it if

Setup reality

We installed sanitize-html 2.17.7 in a clean Node 22 Bookworm sandbox. npm completed in 1.1 seconds, left 20 packages, and used 5 MB. The package itself is 84 KB unpacked, with 7 direct dependencies and 0 peers. npm audit found 0 vulnerabilities at all severities. It is CommonJS without an exports map; both require() and ESM import worked. No TypeScript types were present. Our esbuild browser import was 164.1 KB minified and 63 KB gzipped.

No credential or remote service is involved. Your allowlist is the configuration that matters. Omitting allowedTags keeps the maintained defaults, while providing an array replaces them. Inline style rules only run when style is also an allowed attribute. Anchor CSS regular expressions at both ends, since URLs inside inline styles receive no separate filtering beyond those expressions. Iframe host rules can restrict src to named hosts or domains.

Sanitize again on the trusted server before persistence or rendering, regardless of any browser preview. Keep scripts, styles, and other raw-content elements in nonTextTags. transformTags runs inside the parser but its returned attributes still face the attribute policy. An exclusiveFilter can discard an element and its content, or return excludeTag to keep the content and remove only the wrapper. Set nestingLimit when hostile input can be deeply nested.

Version 2.17.7 protects custom policies that allow SVG animation tags; default tags excluded the vulnerable combination. The two previous releases repaired raw-text handling around SVG, MathML, textarea, and xmp, and the parser upgrade raised the runtime floor to Node 22.12.0. Custom parser settings receive less security coverage than defaults. Avoid decodeEntities: false, keep the opt-in tag set small, and run known browser-mutation payloads through the exact production policy after each update.

Patterns

Clean a fragment with maintained defaults apply-default-policy

const sanitizeHtml = require('sanitize-html')

const clean = sanitizeHtml(untrustedHtml)

Defaults retain common text, list, table, and link markup. Images, iframes, scripts, and inline styles are excluded.

Allow five rich-text tags and safe links replace-with-small-policy

const clean = sanitizeHtml(untrustedHtml, {
  allowedTags: ['p', 'br', 'strong', 'em', 'a'],
  allowedAttributes: { a: ['href', 'title'] },
  allowedSchemes: ['http', 'https', 'mailto'],
})

Providing `allowedTags` replaces the built-in tag list. Omit the option only when the full maintained default is intended.

Add images without copying the whole default extend-default-tags

const clean = sanitizeHtml(untrustedHtml, {
  allowedTags: [...sanitizeHtml.defaults.allowedTags, 'img'],
  allowedAttributes: {
    ...sanitizeHtml.defaults.allowedAttributes,
    img: ['src', 'alt', 'width', 'height', 'loading'],
  },
})

Extend both defaults. Replacing `allowedAttributes` with only the image entry would remove the default attributes for links.

Keep escaped text and discard every wrapper remove-all-markup

const text = sanitizeHtml(untrustedHtml, {
  allowedTags: [],
  allowedAttributes: {},
  disallowedTagsMode: 'discard',
})

`discard` removes tag syntax but keeps child text. It does not add spaces where block elements used to separate content.

Remove rejected elements and their children discard-dangerous-content

const clean = sanitizeHtml(untrustedHtml, {
  allowedTags: ['p', 'strong', 'em'],
  disallowedTagsMode: 'completelyDiscard',
})

`completelyDiscard` removes the content under every rejected wrapper. Use it only when losing nested safe text is acceptable.

Permit two inline CSS properties allow-bounded-styles

const clean = sanitizeHtml(untrustedHtml, {
  allowedTags: ['p', 'span'],
  allowedAttributes: { p: ['style'], span: ['style'] },
  allowedStyles: {
    '*': {
      color: [/^#[0-9a-f]{6}$/i],
      'text-align': [/^(left|center|right)$/],
    },
  },
})

Style rules do nothing until `style` is allowed. URLs inside CSS receive only these regex checks, so anchor every accepted value.

Keep named and prefixed classes restrict-css-classes

const clean = sanitizeHtml(untrustedHtml, {
  allowedTags: ['p', 'code'],
  allowedClasses: {
    p: ['notice', 'warning'],
    code: ['language-*'],
  },
})

A wildcard suffix accepts every class with that prefix. Use an anchored regular expression when the allowed shape is more specific.

Accept embeds from two exact hosts limit-iframe-hosts

const clean = sanitizeHtml(untrustedHtml, {
  allowedTags: ['p', 'iframe'],
  allowedAttributes: { iframe: ['src', 'allowfullscreen'] },
  allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com'],
  allowIframeRelativeUrls: false,
})

Hostnames match exact hosts. If `src` is rejected, the allowed iframe wrapper can remain and may need an empty-element filter.

Add target and rel during sanitization rewrite-external-links

const clean = sanitizeHtml(untrustedHtml, {
  allowedTags: ['a'],
  allowedAttributes: { a: ['href', 'target', 'rel'] },
  transformTags: {
    a: (tagName, attribs) => ({
      tagName,
      attribs: { ...attribs, target: '_blank', rel: 'noopener noreferrer' },
    }),
  },
})

Attributes produced by a transform still pass through `allowedAttributes`. List `target` and `rel` or they will be removed.

Remove empty links and unwrap rejected URLs drop-empty-links

const clean = sanitizeHtml(untrustedHtml, {
  allowedTags: ['p', 'a'],
  allowedAttributes: { a: ['href'] },
  exclusiveFilter(frame) {
    if (frame.tag !== 'a') return false
    if (!frame.attribs.href) return 'excludeTag'
    return !frame.text.trim()
  },
})

Returning `true` removes the tag and content. Returning `excludeTag` keeps its text after URL filtering has removed an unsafe `href`.

Bound work from deeply nested input cap-nesting-depth

const clean = sanitizeHtml(untrustedHtml, {
  allowedTags: ['div', 'p', 'strong', 'em'],
  nestingLimit: 20,
})

A nesting limit bounds pathological depth. Test the value against legitimate editor output before rejecting deeper structure.

Import the separate TypeScript declarations add-community-types

// npm install sanitize-html
// npm install --save-dev @types/sanitize-html
import sanitizeHtml from 'sanitize-html'

const clean: string = sanitizeHtml(input, {
  allowedTags: ['p', 'strong'],
})

Version 2.17.7 bundles no declarations. The default import needs `esModuleInterop`; otherwise use `import * as sanitizeHtml`.

Alternatives

PackageRegistryPick it when
dompurifynpmUse it for browser DOM sanitization, or on a server where jsdom is already an accepted dependency.
xssnpmUse it for a smaller Node allowlist filter when sanitize-html's style and transform controls are unnecessary.
hast-util-sanitizenpmUse it when content already moves through unified or rehype as a HAST tree.

More security guides

cryptography · pyjwt · jose · requests-oauthlib · oauthlib · dompurify · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.