Practical Web Security in React (2026): Prevent XSS, Lock Down CSP, and Reduce Supply Chain Risk
Photo by Markus Spiske on Unsplash
React makes it harder to shoot yourself in the foot than many UI stacks. It escapes text by default, discourages string-based HTML assembly, and pushes developers toward component boundaries that are easier to reason about.
But “harder” isn’t “impossible.”
Most real-world React incidents happen when teams add one small “exception” (HTML rendering, a PDF viewer, a third-party script, or a rushed auth shortcut) and accidentally turn a UI bug into a security event.
This guide is intentionally practical. You’ll get:
- Concrete patterns to prevent XSS (the main frontend blast-radius multiplier).
- CSP guidance that’s strict by default, realistic in production, and measurable.
- Supply chain and dependency hygiene that reduces risk without slowing teams down.
- Token storage guidance that won’t quietly convert an XSS into an account-takeover.
React injection risks: what actually goes wrong in production
React’s default rendering is safe when you treat strings as strings. The failure mode is almost always:
- You render untrusted content as HTML, or
- You bring in a renderer (often PDF or rich text) that becomes a second, less-protected HTML/JS execution surface.
If you remember one rule, make it this:
Any time you convert user-controlled input into HTML (directly or indirectly), you are taking ownership of XSS prevention.
The biggest foot-gun: dangerouslySetInnerHTML
The API name is honest. It’s “dangerous” because it bypasses React’s built-in escaping and tells the browser to interpret a string as HTML.
Avoid it whenever you can. If you must use it, enforce two requirements:
- The HTML cannot include user input, or
- The HTML is sanitized before it reaches React (ideally server-side), with a clear allowlist of tags/attributes.
Here’s a simple anti-pattern:
export function Comment({ bodyHtml }) {
return <div dangerouslySetInnerHTML={{ __html: bodyHtml }} />;
}
If bodyHtml contains something like <img src=x onerror=...> (or other modern payloads), you’re done.
Better options (in descending order of preference):
- Render as text (most “HTML” requirements disappear once you reframe content needs).
- Use a safe markdown renderer with sanitization and an allowlist (and still treat it like untrusted input).
- Sanitize on the server and store “safe HTML” as a separate field that is never mixed with untrusted values.
PDF viewers: the stealthy injection surface
PDF rendering libraries can widen your attack surface because you’re often running:
- A heavy parsing engine (commonly PDF.js under the hood),
- Complex font/image decoding,
- And optionally JavaScript-like behaviors depending on configuration and viewer features.
This is why PDF viewers repeatedly show up in security advisories.
Practical mitigation checklist for React PDF rendering:
- Keep the PDF renderer dependency current (automate updates).
- Disable features you don’t need (annotation editing, scripting, external links, etc., depending on the library).
- Treat PDFs as untrusted unless you control the generation pipeline end-to-end.
- Use defense-in-depth: isolate viewer origin if you can (separate domain/subdomain) and back it with a restrictive CSP.
Test your renderer with a set of known “weaponized” PDFs (use a safe test set in a non-production environment) to validate you aren’t accidentally enabling script execution or dangerous behaviors.
Content Security Policy (CSP): your seatbelt for frontend compromise
Even strong coding practices won’t eliminate every injection risk across a large app. CSP is your “what happens next?” control: it reduces what an injected script can do and blocks many classes of payloads outright.
If you’ve ever said “we’ll fix XSS quickly if it happens,” CSP is how you make “quickly” matter.
A baseline CSP that’s strict and useful
Start with a strict base and relax only what you can justify:
Content-Security-Policy:
default-src 'self';
base-uri 'self';
object-src 'none';
frame-ancestors 'none';
form-action 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
Notes:
object-src 'none'disables old plugin-based execution surfaces.frame-ancestors 'none'prevents clickjacking by default (adjust if you embed your app).connect-srcis where you allow APIs, analytics endpoints, and auth providers.img-src data:is often necessary for inline images/avatars; remove it if you don’t need it.
When you need third-party analytics, fonts, or CDNs, explicitly enumerate them. Keep the list as small as possible and avoid wildcard allowlists.
Don’t stop at “it didn’t break” — measure CSP quality
Two practical improvements teams skip:
- Use report-only first for a short window to understand what you’ll block:
Content-Security-Policy-Report-Only: ...; report-to csp-endpoint;
- Validate the policy using a CSP evaluation tool, and review violations until the policy matches your intended runtime behavior.
If you can: move toward nonces for scripts
The gold standard for CSP with modern frameworks is:
- No inline scripts (unless they carry a valid nonce),
- No “unsafe-eval”,
- Minimal third-party scripts,
- And strong
script-srccontrols.
Nonce-based CSP often takes coordination between app rendering and headers, but it’s one of the highest leverage improvements you can make because it directly constrains the most damaging payload class: arbitrary JavaScript execution.
Supply chain risk: the security problem you inherit at install time
Modern React apps don’t have “a few dependencies.” They have hundreds or thousands once transitive dependencies are included.
npm ls --parseable | wc -l
That count is a sober reminder: every npm install is a trust decision across an ecosystem you don’t fully control.
Practical dependency hygiene that doesn’t slow teams down
You don’t need a perfect program to reduce risk. A “good enough” baseline looks like this:
- Turn on automated patch updates (weekly is a reasonable default).
- Treat major updates as planned work (monthly/quarterly) so you don’t accumulate a brittle dependency cliff.
- Use lockfiles consistently (and don’t mix package managers across developers/CI).
- Audit continuously and route findings to owners (not a shared inbox nobody reads).
If you use GitHub, enable automated dependency update PRs and security alerts so vulnerable packages don’t linger unnoticed.
Choose “reduce blast radius” controls too
Even with great dependency hygiene, assume something slips through eventually. Add compensating controls that limit the damage:
- CSP (limits script execution and exfil paths).
- Least privilege tokens (short-lived access, narrow scopes).
- Server-side authorization checks (never rely on “frontend hides the button”).
- Monitoring for unexpected outbound requests and script errors spikes.
Token storage: how to avoid turning XSS into account takeover
Token storage is one of the most misunderstood areas of frontend security.
Here’s the uncomfortable truth:
If an attacker can run JavaScript in your app, they can read anything JavaScript can read.
That’s why storing sensitive tokens in localStorage or sessionStorage is risky: XSS becomes “steal tokens, impersonate users.”
When httpOnly cookies are the right choice
Use httpOnly, secure, sameSite cookies for session tokens when:
- Your app and API share a domain (or can be configured to),
- You can implement CSRF protections for state-changing requests,
- You want the browser to handle sending tokens automatically.
Tradeoffs to plan for:
- CSRF protection becomes mandatory (e.g., same-site strategy, CSRF tokens, double-submit patterns).
- Cross-domain API calls require careful cookie and CORS configuration.
If you must use web storage, narrow the damage
Sometimes architectures force client-managed tokens (e.g., certain third-party APIs, some mobile-web hybrid constraints).
If you can’t avoid localStorage/sessionStorage:
- Never store refresh tokens in web storage.
- Use short-lived access tokens and rotate frequently.
- Expire aggressively and clear on logout and inactivity.
- Add CSP and prioritize XSS prevention work as a top-tier risk.
A practical React security checklist (copy/paste for your team)
If you want to turn this post into action, start here:
- Rendering
- Remove or strictly gate
dangerouslySetInnerHTML - Sanitize untrusted rich content with allowlists (prefer server-side)
- Review markdown/PDF renderers as high-risk dependencies
- Remove or strictly gate
- CSP
- Ship a strict baseline CSP (report-only first if needed)
- Eliminate inline scripts or enforce nonces
- Validate policy with a CSP evaluator
- Dependencies
- Enable automated version updates
- Triage and patch known vulnerabilities quickly
- Keep a plan for regular major upgrades
- Auth
- Prefer
httpOnlycookies for session tokens - If using storage: short-lived access tokens, no refresh tokens, fast rotation
- Ensure server-side authorization checks for every sensitive action
- Prefer
Frequently asked questions
Is React “secure by default”?
React helps prevent many injection mistakes by escaping text content, but it does not protect you if you render untrusted HTML, add an unsafe renderer, or load risky third-party scripts. You still need CSP and dependency hygiene.
What’s the fastest way to improve security in a React app?
In many real apps, the fastest high-leverage wins are:
- ship a reasonable CSP,
- remove or restrict
dangerouslySetInnerHTML, - and automate dependency updates so vulnerabilities don’t linger.
Does CSP replace secure coding?
No. CSP is defense in depth. You still need to prevent injection bugs, but CSP can limit what an attacker can execute or where data can be exfiltrated if something slips through.
Ready to operationalize web security (and prove it)?
Secure coding practices matter, but security programs don’t scale on good intentions alone—especially when you need to show auditors, customers, or leadership that controls are actually running.
SecureSlate helps you operationalize security and compliance by centralizing policies, evidence, vendor oversight, and recurring workflows—so the work stays current and provable.
If you’re building a security program alongside shipping product, SecureSlate can help you keep the basics tight without drowning in spreadsheets.
Related guides
Need compliance without the complexity?
SecureSlate automates ISO 27001, SOC 2, GDPR, HIPAA, and more. Built for growing teams. See it in action.
No credit card required
May 4, 2026 · CybersecuritySOC 2
Cybersecurity is more important than ever: a practical plan to prevent data breaches
SecureSlate Team
May 4, 2026 · ISO 27001Cybersecurity
How SaaS companies can achieve ISO 27001 certification
SecureSlate Team
Apr 30, 2026 · cybersecurity
The Cyber Essentials UK checklist
SecureSlate Team