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
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
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.