Sanitizing user input is your first line of defense against XSS, but it's not your only one — Content Security Policy is a second, independent layer that tells the browser itself which sources of scripts, styles, and other resources are allowed to execute, so even a sanitization miss doesn't automatically become a working exploit.
Content Security Policy (CSP) is an HTTP header (or meta tag) that instructs the browser to restrict which sources of content — scripts, stylesheets, images, fonts, frames — are allowed to load and execute on a page. It's a defense-in-depth mechanism against cross-site scripting (XSS) and data injection attacks, enforced by the browser regardless of how the malicious content got onto the page.
Why CSP Matters (and When to Deprioritize It)
XSS vulnerabilities are notoriously easy to introduce accidentally (a missed sanitization spot, a third-party widget with its own vulnerability) and hard to fully eliminate through input sanitization alone — CSP acts as a safety net, meaning even if malicious script gets injected into the page, the browser refuses to execute it unless it comes from an explicitly allowed source.
Deprioritize a strict CSP (temporarily) for early-stage prototypes where iteration speed matters more than defense-in-depth — but treat this as genuinely temporary, since retrofitting CSP onto a mature application with many inline scripts and diverse third-party integrations is significantly harder than establishing it from the start.
Getting Started with Content Security Policy
A baseline policy restricting scripts to same-origin and a specific CDN:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;
Setting it in a Next.js middleware or headers config:
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set(
"Content-Security-Policy",
"default-src 'self'; script-src 'self'; object-src 'none';"
);
return response;
}
Core Content Security Policy Concepts Every Developer Should Know
Directives control different resource types independently. script-src controls JavaScript sources, style-src controls CSS, img-src controls images, connect-src controls fetch/XHR/WebSocket destinations, frame-src controls embedded iframes — a comprehensive policy specifies each relevant directive rather than relying solely on default-src as a catch-all.
'unsafe-inline' and 'unsafe-eval' significantly weaken CSP's protection, and are common but risky escape hatches — allowing inline scripts or eval() reopens much of what CSP is designed to prevent. Nonces or hashes are the more secure alternative for legitimately needing specific inline scripts, allowing only that exact script content/nonce rather than all inline scripts.
Content-Security-Policy: script-src 'self' 'nonce-r4nd0mVaLu3';
<script nonce="r4nd0mVaLu3">/* this specific script is allowed */</script>
Report-only mode lets you test a policy without breaking anything, using Content-Security-Policy-Report-Only instead of the enforcing header — violations get reported (to a configured endpoint) without actually being blocked, which is the recommended way to validate a new or tightened policy before enforcing it in production.
Third-party scripts and widgets need to be explicitly allowlisted, and each one you add is effectively an addition to your trusted source list — every third-party integration (analytics, chat widgets, ad scripts) needs deliberate evaluation, since a compromised or malicious third-party script from an allowlisted source bypasses CSP's protection for that source.
Common Content Security Policy Mistakes and How to Fix Them
Mistake 1: using 'unsafe-inline' broadly as a quick fix to avoid CSP violations, undermining most of the actual protection CSP is meant to provide. Fix: use nonces or hashes for legitimately needed inline scripts instead of blanket-allowing all inline content.
Mistake 2: not testing a new policy in report-only mode before enforcing it, risking breaking legitimate functionality (third-party widgets, inline event handlers) in production. Fix: deploy new or changed policies in report-only mode first, review violation reports, then switch to enforcing.
Mistake 3: an overly permissive policy (default-src * or similar) that provides little real protection while creating a false sense of security. Fix: build the policy deliberately, allowlisting only sources you've actually verified are needed and trusted.
When Should You Use Strict CSP with Nonces Instead of Allowlisting Domains?
Use nonce-based CSP when you control script generation server-side and can generate a fresh nonce per request — this is the more secure approach, since it doesn't depend on trusting an entire domain's worth of scripts, just the specific script tagged with the correct nonce. Use domain allowlisting when you're integrating third-party scripts you don't control the generation of, where nonces aren't practically applicable, accepting the broader trust boundary that implies.
Content Security Policy in Production
Roll out new or tightened policies in report-only mode first, monitoring violation reports before switching to enforcement, since CSP misconfiguration can silently break legitimate site functionality if deployed directly to enforcing mode. Also treat every new third-party script integration as a CSP allowlist decision requiring deliberate evaluation, not a rubber stamp.
If your application currently has no CSP header at all, even a moderately permissive baseline policy is a meaningful defense-in-depth improvement worth adding — refine it toward stricter over time rather than waiting for a "perfect" policy before starting.