React

Sanitising input against XSS: what actually works

Input is not cleaned on the way in: encode on output and pass only user-generated HTML through a sanitiser such as DOMPurify.

Key points

  • Encode input on output; only user-generated HTML needs sanitising.
  • React escapes text in JSX, not markup passed to dangerouslySetInnerHTML.
  • Sanitise again on the server, because the client can be modified.

Input is not sanitised on the way in: encode it on output, and pass only user-generated HTML through a sanitiser such as DOMPurify.

Text is not the danger

React, released under the MIT licence, already escapes values: {commento} reaches the DOM as text, not as markup. The hole opens with dangerouslySetInnerHTML, where the string reaches the DOM unchanged.

Encoding or sanitising

function Commento({ testo }) {
  return {testo}; // React escapes the string, no script runs
}

function CommentoRicco({ html }) {
  return ;
}

The first version is safe by construction: the text stays text. The second accepts markup, so it must go through DOMPurify, available under the Apache-2.0 or MPL-2.0 licence, before it touches the DOM.

The rules that hold

  • Encode on output: text becomes entities when it enters the DOM, not when it arrives from the form.
  • Sanitise only HTML: a rich editor needs a parser that strips tags, attributes and URLs with a javascript: scheme.
  • No concatenation: building HTML with + skips every defence, even when the pieces look harmless.

Installation

npm i dompurify

The package needs no configuration: DOMPurify.sanitize uses an allowlist of tags, and you narrow it with ALLOWED_TAGS when the content must stay simple.

Live records in the catalogue

Three components where user input matters: interfaces-textarea, link-preview and input. To browse by category start from 111,985 components.

The limitation

Sanitising in the browser is not enough when the HTML is stored and served to other people: a modified client can send unfiltered markup. The same function has to run on the server, where DOMPurify needs jsdom as its DOM.

Where to continue

The field model is in controlled and uncontrolled forms; the progressive flow is in progressive forms with Next.js; the difference between server actions and an API is in Server Actions vs API.

Frequently asked questions

Is React enough to stop XSS?

For text yes: JSX escapes values. For HTML no: dangerouslySetInnerHTML inserts it as it is, so you need a sanitiser.

Can I sanitise with a regex?

No. A regex does not know HTML grammar and lets through variants such as split attributes or nested entities. You need a parser.

Where should sanitising happen?

On output, at the point where HTML enters the DOM. Repeat it on the server, because the client can be modified.

Does DOMPurify run in Node?

Yes, with jsdom providing the DOM. In the browser it uses the native one. The licence is Apache-2.0 or MPL-2.0.

Should a name or an email be sanitised?

No. Validate them and encode them on output: they are not HTML and must not become HTML.