DOMParser, allow-lists & rebuilding the tree

Sanitize with an allow-list

Lesson 6 of 20 · 14 min · Module 2: Safe HTML and pages

Some features need real formatting from people, such as bold text in a bio or links in a comment. Escaping would show the tags as text, so you need a sanitizer that keeps the safe parts and removes everything else.

Start this lesson →

Free. No installation: you write and run the code in your browser, and it is checked automatically.

By the end, you’ll be able to…

  • Parse HTML safely with DOMParser
  • Rebuild content from allowed elements and attributes
  • Remove dangerous elements together with their content

The idea

Parse the HTML with new DOMParser().parseFromString(html, 'text/html'). The result is an inert document: nothing in it runs and no image loads. Then build a new tree from it: copy only allowed elements, give them only allowed attributes, keep the text of anything else, and leave out dangerous elements such as <script> together with their content.

const doc = new DOMParser().parseFromString('<b onclick="x()">Hi</b>', 'text/html');
const original = doc.body.firstChild;
const copy = document.createElement(original.localName); // <b>, no attributes
copy.append(original.textContent);

01Parse without running

A document made by DOMParser is inert: scripts do not run, images do not load and event attributes do nothing. That makes it a safe place to look at HTML before any of it reaches your page.

02Copy what is allowed

Removing the bad parts from the original is a deny-list in disguise and easy to get wrong. A fresh element made with createElement has no attributes at all, so only what you copy on purpose can appear.

const copy = document.createElement(child.localName); // no attributes
copy.append(clean(child));

03Use a maintained sanitizer in real projects

Attackers know every parser quirk, and sanitizers are easy to get slightly wrong. In production, use a well-tested library such as DOMPurify, or the built-in element.setHTML() that browsers have recently started to support, and keep your allow-list as small as the feature allows.

Your turn

Write sanitize(html). It returns a DocumentFragment built from scratch: text stays text; the elements b, i, em, strong, p and br are copied without any attributes; a is copied only with a safe href (use safeUrl) and otherwise replaced by its content; script, style, template, iframe, object, embed, svg and math are dropped with everything inside; all other elements are replaced by their cleaned content; comments disappear.

What the checks look for

  • Allowed formatting is kept
  • Event attributes and unknown elements disappear
  • Text inside unknown elements is kept
  • Only safe links survive
  • Dangerous elements are removed with their content
  • Comments and nested tricks are cleaned
  • The preview uses your sanitizer
Try it in the editor →

Take this with you

Parse inertly, copy only what is allowed, and drop the rest.

Go a little further

Add rel="nofollow ugc" to every kept link, and allow the class attribute with only the values highlight and note.

Learn Web Security Foundations for free

20 short lessons with real code and instant checks.

See the whole course →