Security Headers

Content Security Policy: A Practical Guide for 2026

Content Security Policy is the most powerful browser security control — and the most complex to implement. This guide walks through CSP for both simple sites and Next.js/React applications.

26 April 20269 min read#CSP#Content Security Policy#XSS

What CSP protects against

Content Security Policy is a browser security standard that controls which resources your page is allowed to load. Its primary purpose is preventing cross-site scripting (XSS) — where an attacker injects malicious JavaScript into your page, which then runs with your page's origin privileges.

A properly configured CSP means that even if an attacker finds an XSS injection point in your application, the browser will refuse to execute injected scripts, preventing cookie theft, credential harvesting, and account takeover.

The basics

CSP is an HTTP response header:

Content-Security-Policy: default-src 'self';

This tells the browser: by default, only load resources from the same origin as the page. Scripts, stylesheets, images, fonts, and connections to external APIs from anywhere else will be blocked.

In practice, your CSP will be more complex than this because most sites use some external resources. Build it incrementally:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://www.googletagmanager.com;
  style-src 'self' https://fonts.googleapis.com 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';

Report-only mode

Before enforcing CSP, use report-only mode to discover what would be blocked:

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

The browser logs all violations to your report endpoint without blocking anything. Review reports for 1–2 weeks to discover legitimate resources you have not whitelisted.

The nonce approach for dynamic sites

If you use inline scripts (common in React/Next.js for hydration), 'unsafe-inline' in script-src defeats much of CSP's XSS protection. The nonce approach lets you allow specific inline scripts without opening the door to injected ones.

Generate a random nonce per request, include it in the header, and add it to trusted script tags:

// HTTP header
Content-Security-Policy: script-src 'nonce-abc123xyz';

// HTML
<script nonce="abc123xyz">/* only this script runs */</script>

In Next.js, implement nonce generation in middleware and pass it through via headers. The Next.js documentation covers this pattern in detail for the App Router.

frame-ancestors vs X-Frame-Options

The frame-ancestors CSP directive controls where your page can be embedded in an iframe. It supersedes X-Frame-Options in modern browsers but you should include both for complete coverage:

Content-Security-Policy: frame-ancestors 'none';
X-Frame-Options: DENY

Common CSP mistakes

  • 'unsafe-inline' in script-src — defeats XSS protection; use nonces instead
  • 'unsafe-eval' in script-src — allows eval() which is a common XSS vector
  • Wildcards like *.googleapis.com — an attacker who can serve content from any subdomain of googleapis.com can bypass your policy
  • Missing base-uri 'self' — without this, an injected <base> tag can redirect relative URLs to an attacker's server
  • Missing frame-ancestors — leaves you vulnerable to clickjacking

Checking your CSP

VP Shield checks your Mozilla Observatory score which covers CSP along with other security headers. An Observatory grade below B usually indicates missing or weak CSP. The scan is free and takes under two minutes.

Check your domain security now

VP Shield runs a free passive scan on any domain — DNS, TLS, email authentication, security headers, subdomain takeover risk. No login, no install, two minutes.