Client-Side XSS Protection

In case you're not familiar, XSS (Cross-site scripting) happens when user input gets rendered as HTML without filtering out scripts, meaning a malicious user can embed some content that does stuff on others accounts when they look at it. The Samy worm used this technique to get the creator over 1,000,000 friends and a lot of angry emails.

This gets a lot more complicated when you decide you might want to have some HTML tags (<h1>, <strong>, <hr>, etc), but still no scripting. A common technique is to parse the content as html, then make sure we don't have any 'bad' tags, attributes, or values (like href="javascript:alert(1)"). Nowadays, most XSS vulnerabilities involve some sort of weird parsing trick, taking advantage of either an obscure part of the W3C spec or some bug in the browser engine.

For this reason, recreating a server-side parser and sanitising input when we get it is impractical. Luckily, there's a much easier solution:

Client-Side Sanitisation

Rather than try to copy what the browser does, we just ask it to parse the html document for us, then walk through it and remove anything we don't like.

You can read more about this approach here. A common way to do this in practice is with a library called dompurify, which ends up being shockingly simple:

import { sanitize } from "DOMPurify";

// ...

elm.innerHTML = sanitize(evilStuff);

Or, in an actual React example:

<p className="content" dangerouslySetInnerHTML={{__html: sanitize(post.content)}}></p>

Simple. Remember you probably also want to filter out CSS and possibly images, so you should still read the docs.

This also doesn't protect you entirely from XSS - For example if you use server side rendering or link to URLs users give you (javascript: is a valid protocol by most regexes).


The main problem with needing to rely on a sanitizer library is that this means the security of every user is in the hands of the small group of maintainers.

This isn't a problem with DOMPurify specifically, but you should keep this in mind and vet each new version before you push it out to your site.

Also keep an eye on the trusted types API, hopefully coming soon to a browser near you.