Content Security Policy (CSP) Header Guide

What Is Content Security Policy?

Content Security Policy (CSP) is a browser security mechanism that lets website operators declare which sources of content are allowed to load and execute. Think of it as an allowlist that the browser enforces — if a script tries to load from a domain not in your CSP, the browser blocks it, regardless of whether the page's HTML referenced it.

CSP is your strongest defense against cross-site scripting (XSS). Even if an attacker injects a <script> tag into your page, a properly configured CSP prevents that script from executing because its source isn't in the allowlist. This is defense in depth — it doesn't replace input validation, but it dramatically reduces the impact of injection vulnerabilities.

CSP is delivered via the Content-Security-Policy HTTP response header. It can also be set via a <meta> tag, but the header is preferred because it covers all resources including those loaded before HTML parsing begins.

Core Directives

CSP policies are composed of directives, each controlling a specific resource type. Here are the directives you'll use most:

Source Values

Each directive takes a space-separated list of source values:

Building a CSP: Step by Step

Start restrictive and open up as needed. Here's a progressive approach:

Step 1 — Observe: Deploy CSP in report-only mode first. The header Content-Security-Policy-Report-Only doesn't enforce anything — it just sends reports to a specified endpoint. This lets you see what would be blocked without breaking anything.

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

Step 2 — Baseline: Once you've collected reports for a few days, build your policy:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://pagead2.googlesyndication.com;
  style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
  font-src 'self' https://fonts.gstatic.com;
  img-src 'self' data: https:;
  connect-src 'self';
  frame-ancestors 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self'

Step 3 — Tighten: Replace 'unsafe-inline' with nonces. Generate a random nonce per request and add it to allowed script/style tags:

// Server generates: nonce="r4nd0mstr1ng123"
Content-Security-Policy:
  script-src 'self' 'nonce-r4nd0mstr1ng123';

<script nonce="r4nd0mstr1ng123">
  // Your inline script — now allowed by CSP
</script>

Nonces vs Hashes vs Unsafe-Inline

The single biggest CSP weakness is 'unsafe-inline' in script-src. It allows any inline script to execute, which means an injected <script>alert(1)</script> still works. To get real XSS protection, you need to eliminate 'unsafe-inline'. Two approaches:

For style-src, 'unsafe-inline' is more commonly accepted because CSS injection is less dangerous than JavaScript injection (though not harmless). Many production sites keep style-src 'unsafe-inline' while tightening script-src.

CSP Reporting

CSP violations can be reported to your server for monitoring. Use the report-to or legacy report-uri directive:

Content-Security-Policy:
  default-src 'self';
  report-to csp-endpoint;

Report-To: {"group":"csp-endpoint","max_age":86400,
  "endpoints":[{"url":"https://your-site.com/api/csp"}]}

The browser sends JSON reports containing the violated directive, the blocked URI, the line number, and the document URI. Aggregate these in a log or dashboard to catch policy gaps and detect injection attempts.

Free services like Report URI and Sentry can collect CSP reports without building your own endpoint. For small sites, this is the easiest path.

Common CSP Mistakes

Inspect your security headers →