Content Security Policy in Practice

Building a policy, nonces vs hashes vs strict-dynamic, a report-only rollout, common breakages, reporting endpoints, the default-src fallback, frame-ancestors, and upgrade-insecure-requests.

A Content Security Policy (CSP) is a response header that declares to the browser 'which origins' resources this page may load.' Its main purpose is mitigating cross-site scripting (XSS) — stopping a malicious script an attacker injected from executing or exfiltrating data. You build a policy by listing directives, separated by semicolons, in the Content-Security-Policy header.

Building a policy: directives and default-src

A policy is made of per-resource-type directives: script-src (scripts), style-src (styles), img-src (images), connect-src (fetch/XHR/WebSocket), and so on. Any type you don't name falls back to default-src. So you design by locking down default-src tightly and opening specific types as exceptions.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data:;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  upgrade-insecure-requests

nonce vs hash vs strict-dynamic

The hardest part is allowing inline scripts safely. 'unsafe-inline' permits every inline script and effectively defeats XSS protection, so avoid it. Use one of three tools instead.

Content-Security-Policy:
  script-src 'nonce-a1b2c3' 'strict-dynamic' https: 'unsafe-inline';
  object-src 'none';
  base-uri 'none'

In that example, a modern browser that supports strict-dynamic trusts only the nonce and ignores https: and 'unsafe-inline', while an older browser that doesn't understand it uses those as a fallback — giving you backward compatibility.

Rolling out with report-only — Enforcing CSP on an existing site right away tends to break the page by blocking legitimate resources too. So deploy first with the Content-Security-Policy-Report-Only header. This mode does not enforce the policy but reports violations only, letting you gather data on real user traffic about what would be blocked, refine the policy, and then flip to enforcement safely.

Reporting endpoints — To receive violation reports, specify a collection path. The old way used report-uri; the modern way defines a named endpoint via the Reporting-Endpoints header and points to it with report-to in the CSP. On a violation, the browser POSTs JSON — the blocked resource, the violated directive, and more — to that endpoint.

Reporting-Endpoints: csp-reports="https://example.com/csp-reports"
Content-Security-Policy-Report-Only:
  default-src 'self'; report-to csp-reports

Common breakages and special directives