What Is Content Security Policy?

Content Security Policy (CSP) is an HTTP response header that tells the browser which sources of content — scripts, styles, images, fonts, frames — are considered trustworthy for a given page. Any resource that doesn't match the policy is blocked before it executes.

CSP is your primary defense against Cross-Site Scripting (XSS) attacks. Even if an attacker injects a script tag into your page, a properly configured CSP will prevent the browser from executing it.

How CSP Works

You deliver a CSP via the Content-Security-Policy HTTP header (or a <meta> tag, though the header is strongly preferred). The browser reads the policy and enforces it for every subresource loaded on the page.

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; img-src *;

This policy says: load everything from the same origin by default, allow scripts from the same origin and one CDN, and allow images from anywhere.

Key Directives Explained

DirectiveControls
default-srcFallback for all resource types not explicitly listed
script-srcJavaScript sources
style-srcCSS sources
img-srcImage sources
connect-srcFetch, XHR, WebSocket endpoints
font-srcFont file sources
frame-srcAllowed iframe sources
form-actionWhere forms can be submitted
upgrade-insecure-requestsAutomatically upgrade HTTP to HTTPS

The Report-Only Mode: Your Best Friend

Rolling out CSP on an existing site is notoriously tricky — one wrong directive breaks legitimate functionality. The solution is Content-Security-Policy-Report-Only:

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

In report-only mode, the browser simulates the policy and sends violation reports to your endpoint without actually blocking anything. You can collect violations for days or weeks, then tune your policy before enforcing it.

Nonces and Hashes: Moving Beyond 'unsafe-inline'

Many developers default to 'unsafe-inline' to allow inline scripts, which defeats much of CSP's purpose. The modern approach uses nonces or hashes:

  • Nonce: Your server generates a cryptographically random value per request, adds it to the CSP header, and adds a matching nonce attribute to legitimate script tags. Only scripts with the matching nonce execute.
  • Hash: Compute the SHA-256 hash of an inline script and include it in the CSP. The browser computes and compares the hash before executing.
Content-Security-Policy: script-src 'nonce-r4nd0mV4lu3=='
<script nonce="r4nd0mV4lu3==">/* legitimate code */</script>

Common Mistakes to Avoid

  • Using 'unsafe-eval' to allow eval() — this re-opens the door to many attacks.
  • Wildcard sources like script-src * — this is equivalent to having no policy.
  • Forgetting frame-ancestors to prevent clickjacking (use it alongside the X-Frame-Options header).
  • Not testing your policy in report-only mode before enforcing.

Testing Your CSP

Use the Google CSP Evaluator to analyze your policy for weaknesses. Browser DevTools (Network tab → response headers, and the Console for CSP violations) are indispensable during development.

A strong CSP, combined with HTTPS, HTTP security headers like X-Content-Type-Options, and input validation, forms a robust layered defense for any web application.