How to Secure useInsertionEffect in React
This article explains how to secure the
useInsertionEffect hook in React to protect your
applications from security vulnerabilities like Cross-Site Scripting
(XSS) and CSS injection. You will learn the security risks associated
with dynamic style injection, how to safely handle dynamic styling, and
how to configure Content Security Policies (CSP) to safeguard your
runtime styles.
Understand the Security Risks of useInsertionEffect
Introduced in React 18, useInsertionEffect is a
specialized hook designed exclusively for CSS-in-JS library authors. It
allows the injection of <style> tags into the DOM
before any layout effects run.
Because this hook inserts raw CSS styles directly into the document, it poses a significant security risk if not handled correctly. If untrusted user input is interpolated directly into the injected stylesheet, attackers can exploit this via CSS Injection. Attackers can use malicious CSS to exfiltrate sensitive data (such as CSRF tokens or input values via background image URLs) or execute JavaScript in older browsers.
Rule 1: Never Inject Untrusted User Input
The most effective way to secure useInsertionEffect is
to avoid passing any user-generated or dynamic, unvalidated input into
the style injection mechanism.
Instead of interpolating raw strings from user inputs directly into your style sheets, use a predefined map of safe tokens or themes.
// UNSAFE: Do not do this
useInsertionEffect(() => {
const style = document.createElement('style');
// If userColor comes from a database or query param, this is vulnerable
style.textContent = `.dynamic-class { color: ${userColor}; }`;
document.head.appendChild(style);
return () => document.head.removeChild(style);
}, [userColor]);
// SECURE: Use a whitelist of approved values
const SAFE_COLORS = {
red: '#ff0000',
blue: '#0000ff',
green: '#008000'
};
useInsertionEffect(() => {
const verifiedColor = SAFE_COLORS[userColor] || '#000000';
const style = document.createElement('style');
style.textContent = `.dynamic-class { color: ${verifiedColor}; }`;
document.head.appendChild(style);
return () => document.head.removeChild(style);
}, [userColor]);Rule 2: Implement a Strict Content Security Policy (CSP)
To mitigate the risk of unauthorized style injections, implement a robust Content Security Policy (CSP) on your web server. Your CSP should restrict where styles can be loaded from and how inline styles are treated.
Use the style-src directive to control style sheets. If
your CSS-in-JS library supports it, configure a cryptographic
nonce (a number used once) that React or your CSS-in-JS library
attaches to every injected <style> tag.
Example CSP Header:
Content-Security-Policy: default-src 'self'; style-src 'self' 'nonce-random123';
When using useInsertionEffect, ensure the created
element includes this nonce:
useInsertionEffect(() => {
const style = document.createElement('style');
style.setAttribute('nonce', 'random123'); // Match your CSP nonce
style.textContent = `.secure-box { padding: 10px; }`;
document.head.appendChild(style);
return () => document.head.removeChild(style);
}, []);Rule 3: Use Sanitization Libraries for Dynamic CSS
If your application absolutely must accept dynamic styling
coordinates or dimensions from a user database, sanitize the inputs
before injection. Use helper utilities or parsing libraries that strip
out malicious properties, such as behavior,
expression, or external url() resource
imports.
Ensure that value inputs are strictly typed or parsed using helper functions:
// Ensure a value is strictly a number/pixel unit before injection
const sanitizePixels = (input) => {
const parsed = parseInt(input, 10);
return isNaN(parsed) ? '0px' : `${parsed}px`;
};Rule 4: Restrict Usage to CSS-in-JS Libraries
According to the official React documentation,
useInsertionEffect has a very narrow use case and should
not be used in everyday application component development.
To minimize your application’s attack surface: * Use standard CSS,
CSS Modules, or Tailwind CSS for normal application styling. * If you
need to perform dynamic styling in a regular component, use the standard
style attribute (e.g.,
style={{ color: userColor }}) instead of injecting raw
stylesheets via useInsertionEffect. React automatically
sanitizes styles passed through the style prop.