Why is this an issue?

DOMPurify is a widely used DOM-based library for sanitizing various markup languages against cross-site scripting (XSS) attacks. However, certain configuration options can weaken or completely bypass its protective mechanisms, rendering the sanitization ineffective.

Adding dangerous tags or attributes to the DOMPurify allowlist, disabling critical sanitization features like SAFE_FOR_XML or SANITIZE_DOM, or enabling risky options like ALLOW_UNKNOWN_PROTOCOLS can allow malicious scripts to pass through the sanitizer and execute in the user’s browser.

How to fix it in React

Code examples

The following code is vulnerable because DOMPurify is configured with options that weaken its sanitization before injecting content into a React component.

Noncompliant code example

import DOMPurify from 'dompurify';

function Comment({ html }) {
    const clean = DOMPurify.sanitize(html, {
        ADD_TAGS: ['script', 'iframe'],      // Noncompliant
        ADD_ATTR: ['onclick', 'onerror'],    // Noncompliant
    });
    return ;
}

Compliant solution

import DOMPurify from 'dompurify';

function Comment({ html }) {
    const clean = DOMPurify.sanitize(html, {
        USE_PROFILES: { html: true },
        ADD_TAGS: ['custom-element'],
        ADD_ATTR: ['data-custom'],
    });
    return ;
}

How does this work?

Safe DOMPurify configuration

The compliant solution restricts DOMPurify to HTML-only using USE_PROFILES and only adds safe custom elements and data attributes to the allowlist. It avoids allowing any tags or attributes that can execute JavaScript.

Here is a list of the exhaustive checks you need to do:

Option Risk Best practice

ADD_TAGS / ALLOWED_TAGS / FORBID_TAGS

DOM XSS via executable elements

Never allow script, iframe, object, embed, form, input, textarea, select, meta, link, style, base, svg, math

ADD_ATTR / ALLOWED_ATTR / FORBID_ATTR

DOM XSS via event handlers

Never allow on* attributes (onclick, onerror, onload, etc.)

SAFE_FOR_XML

Mutation XSS (mXSS)

Keep set to true

SANITIZE_DOM

DOM Clobbering

Keep set to true

ALLOW_UNKNOWN_PROTOCOLS

javascript: URI injection

Keep set to false

WHOLE_DOCUMENT

Dangerous head elements

Keep set to false

RETURN_TRUSTED_TYPE

Trusted Types bypass

Keep set to true

ADD_URI_SAFE_ATTR

1-click XSS via javascript: URI

Never add URI-carrying attributes (href, src, action, formaction, xlink:href, data)

ALLOWED_URI_REGEXP

URI allowlist bypass via partial regex match (e.g. /http:\/\/safe\.com/ matches javascript:alert(1)//http://safe.com)

Always anchor the pattern with ^ (e.g. /^https?:/)

How to fix it in Express.js

Code examples

The following code is vulnerable because DOMPurify is configured with options that weaken its sanitization before sending content in an Express response.

Noncompliant code example

const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const DOMPurify = createDOMPurify(new JSDOM('').window);

app.post('/comment', (req, res) => {
    const clean = DOMPurify.sanitize(req.body.comment, {
        ADD_TAGS: ['script', 'iframe'],      // Noncompliant
        ADD_ATTR: ['onclick', 'onerror'],    // Noncompliant
    });
    res.send(`${clean}`);
});

Compliant solution

const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const DOMPurify = createDOMPurify(new JSDOM('').window);

app.post('/comment', (req, res) => {
    const clean = DOMPurify.sanitize(req.body.comment, {
        USE_PROFILES: { html: true },
        ADD_TAGS: ['custom-element'],
        ADD_ATTR: ['data-custom'],
    });
    res.send(`${clean}`);
});

How does this work?

Safe DOMPurify configuration

The compliant solution restricts DOMPurify to HTML-only using USE_PROFILES and only adds safe custom elements and data attributes to the allowlist. It avoids allowing any tags or attributes that can execute JavaScript.

Here is a list of the exhaustive checks you need to do:

Option Risk Best practice

ADD_TAGS / ALLOWED_TAGS / FORBID_TAGS

DOM XSS via executable elements

Never allow script, iframe, object, embed, form, input, textarea, select, meta, link, style, base, svg, math

ADD_ATTR / ALLOWED_ATTR / FORBID_ATTR

DOM XSS via event handlers

Never allow on* attributes (onclick, onerror, onload, etc.)

SAFE_FOR_XML

Mutation XSS (mXSS)

Keep set to true

SANITIZE_DOM

DOM Clobbering

Keep set to true

ALLOW_UNKNOWN_PROTOCOLS

javascript: URI injection

Keep set to false

WHOLE_DOCUMENT

Dangerous head elements

Keep set to false

RETURN_TRUSTED_TYPE

Trusted Types bypass

Keep set to true

ADD_URI_SAFE_ATTR

1-click XSS via javascript: URI

Never add URI-carrying attributes (href, src, action, formaction, xlink:href, data)

ALLOWED_URI_REGEXP

URI allowlist bypass via partial regex match (e.g. /http:\/\/safe\.com/ matches javascript:alert(1)//http://safe.com)

Always anchor the pattern with ^ (e.g. /^https?:/)

Keep jsdom up to date

When running DOMPurify server-side, always use the latest version of jsdom. Older versions of jsdom have known bugs that can result in XSS even when DOMPurify works correctly. Never use happy-dom as an alternative, as it is not considered safe for sanitization purposes.

Resources

Documentation

Standards