Open-source project
cure53/DOMPurify avatar
cure53/DOMPurify

DOMPurify: the DOM-only XSS sanitizer, and where it stops protecting you

DOMPurify - a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG. DOMPurify works with a secure default, but offers a lot of configurability and hooks. Demo:

17,413 stars860 forksJavaScriptApache-2.0

At a glance

What is it?
DOMPurify turns dirty HTML into clean HTML by leaning on the browser's own parser. It is easy to adopt and hard to misuse, but the documentation is explicit that whatever you do to the markup after sanitization can undo it.
Who is it for?
Adopt DOMPurify when untrusted HTML has to reach the DOM and you control every step between the sanitize call and the sink. Do not adopt it as a substitute for a Content Security Policy, for sanitizing on the server in a parser that differs from the browser's, or for markup you intend to mutate afterwards.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem DOMPurify solves, and who actually needs it

Any application that renders HTML it did not author has an XSS problem. Comments, support tickets, CMS bodies, markdown output, pasted content from a rich text editor: all of it ends up in an element's innerHTML somewhere, and all of it can carry a script tag, an event handler attribute, or a javascript: URL. Writing a blocklist for that by hand is a losing game, because the browser's HTML parser accepts far more malformed input than most developers expect.

DOMPurify takes the opposite approach. The README describes it as a DOM-only sanitizer for HTML, MathML and SVG, and the phrase DOM-only is the design decision that matters. It does not parse your string with a regular expression or a hand-written tokenizer. It hands the markup to the browser, lets the real parser build a tree, walks that tree, removes what is dangerous, and serializes the result back to a string. Anything the browser would have interpreted as executable is therefore visible to DOMPurify as a node or an attribute, not as a cleverly escaped substring.

The intended audience is front-end and full-stack engineers who already have a place where untrusted markup meets the DOM. If your application never renders third-party HTML, DOMPurify is not for you. If it does, the project's own framing is that the authors come from a web-attack background and that the sanitizer has a documented bypass history, which is a more honest starting point than most security libraries offer.

How DOMPurify sanitizes: the browser parser as the security boundary

The mechanism is a parse, walk, serialize cycle. You pass a string, and DOMPurify creates a document from it using the platform's own parsing rules. That step is what makes the approach resilient: parser-mutation tricks, namespace confusion between HTML and SVG, DOM clobbering, and template element abuse are all described on the project's Attack Classes & Bypass History wiki page as classes it defends against, and they are only detectable because the tree already exists in the shape the browser would have produced.

After parsing, DOMPurify applies an allowlist. The default configuration permits HTML, SVG and MathML, which is broader than many teams assume. The README points out that if you only need HTML, you can narrow this with a profile, and that is the configuration most content-rendering applications should be running. The return value is a string by default, though the README notes that sanitize() also accepts a DOM node such as an Element, DocumentFragment or Document.

The part that is not a mechanism but a contract: DOMPurify only guarantees the state of the markup at the moment it returns. The README states plainly that if you sanitize first and modify afterwards, you may void the effects of sanitization, and that feeding sanitized markup into another library is only safe if that library does not reshape the HTML on its own. That is the single most important sentence in the documentation and the one most integrations ignore.

Installing DOMPurify from npm and running a first sanitize

The package is published on npm as dompurify. Installing it puts the ESM, CommonJS and UMD builds in dist/, which is what the repository's build scripts produce.

bash
npm install dompurify

In a module context, import the default export and call sanitize with the untrusted string. The README's own example returns clean HTML as a string, which you can then assign to innerHTML yourself.

js
import DOMPurify from 'dompurify';

const clean = DOMPurify.sanitize('<b>hello there</b>');

For a plain script tag deployment, the README loads the UMD build from dist/ and then calls the global. The minified file is the one the project describes as the tested production version.

html
<script type="text/javascript" src="dist/purify.min.js"></script>
<script type="text/javascript">
  const clean = DOMPurify.sanitize(dirty);
</script>

If your application renders only HTML and never SVG or MathML, restrict the profile at the call site rather than relying on the default. This is the configuration the README gives for that case.

js
const clean = DOMPurify.sanitize(dirty, { USE_PROFILES: { html: true } });

What you should see in every case is a string with the dangerous parts removed. What you should not do is treat that string as immutable: assign it to the DOM once, and do not pass it through a template engine, a markdown renderer, or a second sanitizer afterwards.

The failure mode DOMPurify cannot fix for you

DOMPurify is a library, not a policy. The README's foot-gun section is unusually direct about this: sanitize, then modify, and you may have undone the sanitization. A concrete shape of that mistake is sanitizing a comment body, then running the result through a client-side markdown renderer that re-parses and re-emits HTML. The second pass is outside DOMPurify's control, and the output is no longer something DOMPurify vouched for.

A second limitation is environmental. DOMPurify is DOM-only, which means it needs a DOM to work against. In a browser that is free. On a server, the README points to jsdom as the environment the project tests against, and the test scripts in package.json confirm that jsdom and happy-dom runners exist. Running DOMPurify under a DOM emulation means the parser doing the work is the emulation's parser, not the one your users' browsers run. For markup that will be rendered in a browser, sanitizing in the browser is the configuration where the parse that DOMPurify inspected and the parse that renders are the same parse.

A third boundary is legacy support. The README states that DOMPurify does not break on MSIE or other legacy browsers, it simply does nothing, and that v2.5.9 is the last version supporting MSIE, with the 2.x branch carrying security updates for it. If you still ship to Internet Explorer, the 3.x line is not a drop-in answer.

DOMPurify compared with sanitize-html

The most common comparison developers reach for is sanitize-html. The difference is architectural rather than a matter of option lists. sanitize-html parses the input itself in JavaScript and applies its own allowlist to the resulting tree, which means it runs anywhere Node runs without needing a DOM implementation, and its output does not depend on which browser engine is present.

DOMPurify does the opposite: it delegates parsing to the host environment and filters the tree the host produced. That is what makes it tolerant of malformed markup and what the README means by turning browser technologies into an XSS filter. The trade-off is that its correctness is tied to the parser underneath it, which is why the project runs its suite on Chromium, Firefox and WebKit across Ubuntu, macOS and Windows, plus older engine snapshots going back roughly three years, and why server-side use requires jsdom or happy-dom.

Neither approach is strictly safer. If you need one sanitizer whose behaviour is identical in a Node worker and in a browser tab, the self-parsing model is easier to reason about. If you need to match what the browser will actually build from hostile input, doing the filtering on the browser's own tree is the closer fit. The project also notes that it inspired the HTML Sanitizer API, which is now shipping in browsers and being standardized in the WHATWG HTML specification, so the long-term direction is a platform primitive rather than a library.

Versioning, licensing and the maintenance cost you are signing up for

The repository is not archived, and the last push was on 2026-09-19. The release cadence visible in the release list is roughly every two to three weeks across 3.4.13, 3.4.14 and 3.4.15, with 3.4.15 published on 2026-09-06. For a security component that cadence is the point, and it is also the cost: a pinned version drifts out of date quickly, and the project maintains a separate 2.x branch for MSIE users, which means two lines to track if your estate is mixed.

The licence situation needs attention because the repository is not uniform. The GitHub metadata lists Apache-2.0, but the README's badge reads MPL-2.0 OR Apache-2.0, and the repository root contains both a LICENSE and a LICENSE-MPL file. If your organization has rules about MPL-2.0 code, read both files and confirm which one applies to the artifact you are consuming before you ship. This is a packaging detail, not a legal opinion, and the files are short enough to check directly.

The upgrade cost itself is low in the common case: the public surface is one sanitize function plus a configuration object and hooks, and the README documents persistent configuration and hooks as first-class features. The expensive part is not the upgrade, it is the review. Every bump to a sanitizer is a change to your security boundary, and the project publishes a SECURITY.md and runs an OpenSSF Scorecard workflow, so there is a documented process to follow when a bypass is reported.

Editorial conclusion

Adopt DOMPurify when untrusted HTML has to reach the DOM and you control every step between the sanitize call and the sink. Do not adopt it as a substitute for a Content Security Policy, for sanitizing on the server in a parser that differs from the browser's, or for markup you intend to mutate afterwards. Before shipping, read the Security Goals & Threat Model wiki page, confirm which profile you need (the default permits HTML, SVG and MathML), and pin a version: the repository publishes releases frequently, and the 2.x branch is the only one that supports MSIE.

Frequently asked questions

What is DOMPurify used for?

It sanitizes HTML and prevents XSS attacks, returning clean HTML from dirty input. The README describes it as a DOM-only sanitizer for HTML, MathML and SVG, and notes the result can be written into the DOM with innerHTML or document.write.

How do I install DOMPurify?

It is published on npm as dompurify, so npm install dompurify is the package route. For a script tag deployment the README loads dist/purify.min.js, which it calls the minified and tested production version, and then calls DOMPurify.sanitize.

How do I use DOMPurify sanitize in JavaScript?

Import the default export and pass the untrusted string to sanitize, as in const clean = DOMPurify.sanitize(dirty). If you only render HTML, the README shows passing { USE_PROFILES: { html: true } } as the second argument to narrow the default, which otherwise permits HTML, SVG and MathML.

Is DOMPurify safe to use?

The README states that sanitizing and then modifying the markup afterwards may void the effects of sanitization, and that passing sanitized output to another library is only safe if that library does not reshape the HTML itself. Within that boundary the project documents its threat model and its bypass history on two wiki pages it asks users to read.

What is the current version of DOMPurify?

The README states that the project has reached version 3.4.15, and the release list shows 3.4.15 published on 2026-09-06. The README also notes that v2.5.9 is the latest version supporting MSIE, with the 2.x branch carrying security updates for it.

Official sources

  1. cure53/DOMPurify on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cure53-dompurify.svg)](https://hysenlabs.com/projects/cure53-dompurify)