Safeguard
Security Guides

React Security Best Practices: A Practical Checklist for 2026

React escapes JSX text for you, but XSS sinks, secrets in the client bundle, token storage, and a 500-package npm worm are still yours to handle.

Daniel Osei
Security Researcher
5 min read

The most dangerous assumption a React team can make is "React handles security for us." It handles one thing extremely well — escaping text interpolated into JSX — and that single default has quietly prevented a decade of XSS. Everything else is on you: the raw-HTML sinks React deliberately leaves open, the secrets that end up in a shipped bundle, where you store auth tokens, the third-party <script> tags marketing added, and the sprawling npm dependency tree underneath it all. This is a working checklist, not a lecture — ten practices grouped by where React's protection ends and yours begins.

1. Know exactly where React's auto-escaping stops

Values inside JSX curly braces are escaped, so {userComment} renders a <script> payload as inert text. That protection does not cover: raw-HTML injection via dangerouslySetInnerHTML, URL-bearing attributes like href and src, or anything you write to the DOM manually with a ref. Treat those three as the only XSS surfaces worth arguing about in a React review.

2. Sanitize every dangerouslySetInnerHTML at render time

If you must render HTML you didn't author — CMS content, markdown output, rich-text — run it through DOMPurify at the moment of render, not when it was stored (your allowlist can change between storage and display):

import DOMPurify from "dompurify";

function Article({ html }) {
  const clean = DOMPurify.sanitize(html);
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

3. Validate URL schemes before they hit an attribute

<a href={url}> with url = "javascript:stealCookies()" executes — escaping HTML entities does nothing to a URL scheme. Allowlist schemes explicitly:

function safeHref(url) {
  try {
    const u = new URL(url, window.location.origin);
    return ["https:", "http:", "mailto:"].includes(u.protocol) ? url : "#";
  } catch {
    return "#";
  }
}

4. Stop shipping secrets in the bundle

Anything bundled into your React app is public. API keys, tokens, and internal endpoints prefixed into the build (REACT_APP_*, VITE_*) are readable by anyone who opens DevTools. Client-side environment variables are configuration, never secrets — keep real credentials on a backend the browser talks to.

5. Store auth tokens where XSS can't trivially exfiltrate them

localStorage is readable by any JavaScript on the page, so a single XSS turns into full token theft — the injected script simply reads the key and posts it to an attacker server. Prefer HttpOnly, Secure, SameSite cookies for session tokens so injected scripts can't read them directly. This does not make you CSRF-immune — pair cookies with anti-CSRF tokens or SameSite=Strict/Lax and origin checks. If your architecture forces tokens into JavaScript-accessible storage, keep access tokens short-lived, rotate refresh tokens, and treat every XSS defense above as load-bearing rather than optional, because the token store is now only as safe as your weakest injection point.

6. Deploy a Content-Security-Policy

CSP is your safety net for the XSS that slips past code review. A strict policy blocks inline scripts and restricts sources:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; object-src 'none'; base-uri 'self'

Avoid unsafe-inline — it neutralizes the whole policy. Use nonces or hashes for the inline scripts you genuinely need.

7. Lock down third-party scripts

Every analytics, chat-widget, or A/B-testing <script> runs with full access to your DOM and tokens. Load them from pinned versions, add Subresource Integrity (integrity + crossorigin) where the vendor supports it, and keep an inventory — a compromised third-party script is indistinguishable from your own XSS to the browser.

8. Render user content, never user components

Passing untrusted data into React.createElement type positions, dynamic dangerouslySetInnerHTML, or eval-adjacent patterns invites injection. Keep the component tree static and driven by trusted logic; only data should come from users. The same discipline applies to routing and redirects: an open redirect built from a user-supplied returnUrl lets an attacker bounce victims to a phishing origin under your domain's trust, so validate redirect targets against an allowlist of known paths rather than reflecting whatever the query string contains.

9. Treat the npm tree as attack surface

2025 made this undeniable: the self-propagating Shai-Hulud worm compromised 500+ npm packages (CISA, September 2025), and a phishing-driven maintainer takeover pushed malicious chalk and debug releases. Your React app inherits every transitive dependency. Run continuous software composition analysis on the full dependency graph, not an occasional npm audit.

10. Gate it in CI, not in a wiki

Security practices that live in documentation decay. Put the checks in the pipeline: a CLI scan that fails the build on new criticals makes the standard enforceable instead of aspirational.

Quick reference

RiskPractice
Stored/reflected XSSSanitize dangerouslySetInnerHTML with DOMPurify
javascript: URLsAllowlist schemes on href/src
Leaked secretsNo credentials in the client bundle
Token theft via XSSHttpOnly cookies over localStorage
Inline-script XSSStrict, nonce-based CSP
Third-party script compromisePin + SRI + inventory
Malicious dependencyContinuous SCA + reachability

How Safeguard Helps

Safeguard maps your actual React dependency graph and runs reachability analysis, so you patch the CVEs your code truly invokes instead of triaging noise. Griffin, our AI engine, flags behavioral anomalies in new package versions — the surprise install scripts and obfuscated payloads seen in the 2025 npm attacks — before they land. When a fix exists, auto-fix opens a tested pull request with the minimal safe bump. And a dynamic scan can confirm your CSP and header defenses actually hold in the running app. Sanitizing sinks, guarding secrets, and choosing token storage stay your responsibility — Safeguard covers the dependency half you can't read by hand.

Ship React with confidence — get started free, read the documentation, or compare Safeguard to the alternatives.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.