How to Secure Virtual DOM in React

React’s Virtual DOM provides built-in protections against common web vulnerabilities, but developers must still implement specific practices to ensure complete security. This article explains how the Virtual DOM handles data rendering, identifies the primary security risks associated with it, and provides actionable steps to secure your React applications from Cross-Site Scripting (XSS) and injection attacks.

React’s Default Security Behavior

By default, React secures the Virtual DOM by auto-escaping values before rendering them to the browser. If a user inputs a malicious script, React treats it as a string rather than executable code.

For example, the following code is safe in React:

const userInput = "<script>alert('hack')</script>";
// React renders this as text, not executable script
return <div>{userInput}</div>;

React automatically converts HTML tags into safe string entities, neutralizing potential Cross-Site Scripting (XSS) payloads. However, there are specific scenarios where this protection is bypassed.

1. Avoid or Secure dangerouslySetInnerHTML

The most common way to bypass React’s default escaping is by using the dangerouslySetInnerHTML attribute. This prop inserts raw HTML directly into the DOM, which makes your application highly vulnerable to XSS if the data source is untrusted.

The Solution: Sanitize with DOMPurify

If you must render raw HTML, you must sanitize the input before passing it to dangerouslySetInnerHTML. Use a trusted library like DOMPurify to strip malicious code.

import DOMPurify from 'dompurify';

const rawHtml = "<img src=x onerror=alert('hack')>";
const cleanHtml = DOMPurify.sanitize(rawHtml);

function SafeComponent() {
  return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />;
}

2. Prevent Attribute Injection

React secures element content, but it does not fully secure element attributes. Attackers can inject malicious scripts through links (href attributes) or source paths (src attributes).

The Danger of javascript: URIs

If you allow users to input URLs, they can insert executable JavaScript:

// If userInput is "javascript:alert('hack')"
<a href={userInput}>Click Here</a>

The Solution: Validate and Sanitize URLs

Always validate and sanitize user-provided URLs before rendering them. Restrict URLs to safe protocols like http: and https:.

function safelyValidateUrl(url) {
  try {
    const parsedUrl = new URL(url);
    if (['http:', 'https:'].includes(parsedUrl.protocol)) {
      return url;
    }
  } catch (e) {
    // Handle invalid URL
  }
  return '#'; // Fallback safe URL
}

3. Secure Server-Side Rendering (SSR) Hydration

If you use Server-Side Rendering (SSR) with frameworks like Next.js, you often need to send state from the server to the client. This is typically done by serializing data into a JSON string and placing it inside a <script> tag in the initial HTML.

If this state contains user input that has not been escaped, attackers can inject scripts into the initial page load before the Virtual DOM even mounts.

The Solution: Use Serialization Libraries

Do not use simple JSON.stringify() directly inside a script tag. Instead, use serialization libraries like serialize-javascript to safely escape HTML entities and script tags during the SSR process.

import serialize from 'serialize-javascript';

// Safe rendering on the server
const safeState = serialize(initialState);

4. Do Not Directly Manipulate the Real DOM

Using React refs (useRef) to directly access and manipulate the real DOM bypasses the Virtual DOM’s automatic escaping features.

Avoid patterns like:

// Avoid this
myRef.current.innerHTML = userInput;

If you must manipulate the DOM directly using refs, treat it as raw HTML and sanitize the inputs using DOMPurify before insertion.