← Thin Margins

Rolling out a Content Security Policy without breaking the site

2026-09-05

A Content Security Policy tells the browser which sources of script, style and other resources a page may use. Written all at once, it usually blocks something you forgot about. Rolled out in steps, it rarely does.

Step 1: report only

Start with a strict policy that the browser reports on but does not enforce:

Content-Security-Policy-Report-Only:
  default-src 'self'; img-src 'self' data:; object-src 'none';
  base-uri 'self'; frame-ancestors 'none'; report-to csp
Reporting-Endpoints: csp="/csp-report"

Leave it for a week. Every violation arrives as a JSON report naming the directive and the blocked URL. Most of them are legitimate resources to add to the policy; some are browser extensions injecting scripts, which you can ignore.

Step 2: replace inline scripts with nonces

Inline <script> blocks are the usual obstacle. Allowing 'unsafe-inline' defeats most of the protection, so give each inline script a nonce generated per response:

Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'self'
<script nonce="r4nd0m">...</script>

The nonce must be unpredictable and different on every response, which means the HTML cannot be cached as a static file. For a fully static site, hashes ('sha256-...') of each inline script do the same job without that cost.

'strict-dynamic' lets a trusted script load further scripts, so a loader with a nonce can pull in its dependencies without listing every domain.

Step 3: enforce, keep reporting

Switch the header name to Content-Security-Policy and keep the reporting endpoint. New violations after a deploy usually point to a third-party snippet someone added without telling anyone.

Directives worth setting on every site