DOMPurify
DOMPurify is the de-facto JavaScript library for sanitising HTML against XSS. When you must accept rich user HTML (rich-text editors, markdown, oEmbed), pass it through DOMPurify before assigning to innerHTML.
Sanitise on the client, configure tags, hooks
EXAMPLE
import DOMPurify from 'dompurify';
// 1) Defaults — already very strict
const dirty = `<img src=x onerror=alert(1)><p>hello</p>`;
const clean = DOMPurify.sanitize(dirty);
// → "<img src=\"x\"><p>hello</p>" — the onerror is gone
element.innerHTML = clean; // safe to assign now
// 2) Allow a specific tag list (e.g. lightweight markdown output)
const safe = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['a', 'b', 'i', 'em', 'strong', 'p', 'br', 'ul', 'ol', 'li', 'code', 'pre'],
ALLOWED_ATTR: ['href', 'title'],
ALLOW_DATA_ATTR: false,
});
// 3) Force every link to open in a new tab + lose referrer
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
if (node.tagName === 'A' && node.hasAttribute('href')) {
node.setAttribute('target', '_blank');
node.setAttribute('rel', 'noopener noreferrer');
}
});
const safer = DOMPurify.sanitize(html);
// 4) Strip dangerous URI schemes — block javascript:, data: (mostly)
DOMPurify.addHook('uponSanitizeAttribute', (node, data) => {
if (data.attrName === 'href' || data.attrName === 'src') {
if (/^\s*(javascript|data|vbscript):/i.test(data.attrValue)) {
data.keepAttr = false;
}
}
});
// 5) Reject anything BUT a known set of tags / attrs (deny-by-default)
const veryStrict = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em'],
ALLOWED_ATTR: [],
});
// 6) Server-side — JSDOM + DOMPurify (Node)
// npm i isomorphic-dompurify jsdom
import DP from 'isomorphic-dompurify';
const safeOnServer = DP.sanitize(userHtml);
// 7) Common framework integrations
// React — assign through dangerouslySetInnerHTML
function RichBody({ html }) {
const clean = useMemo(() => DOMPurify.sanitize(html), [html]);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
// Vue — v-html (still call DOMPurify first)
<div v-html="sanitisedBody"></div>
// 8) DON'T do these
// element.innerHTML = userHtml // raw injection
// element.innerHTML = userHtml.replace('<', '<') // half-baked, never enough
Why it matters
Sanitise once, at the boundary — either when storing OR when rendering, not both. Most teams sanitise on render (so the raw HTML stays intact for future re-sanitising as DOMPurify improves).
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Browser
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userHtml);
// Node (with jsdom)
import createDOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
const DOMPurify = createDOMPurify(new JSDOM('').window);
Try it Yourself »
Discussion
Loading…