CSP Academy

← The ten steps · Step 2 of 10 · 5 minutes

Deploy one today and break nothing

There is a version of CSP that cannot break your site. It is the version you should deploy first, and almost every tutorial buries it near the end.

The header that changes nothing

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

Same syntax, same directives, one difference: the browser enforces nothing. It works out what would have been blocked, tells you, and then loads it anyway. Your site behaves exactly as it did before.

This is the single most useful thing in the whole specification, and it is why you never have to guess at a policy. You do not need to know what your pages load. You need to ask the browsers that already know.

Why guessing does not work

Every team that writes a policy by reading their own code discovers the same thing: the code is not the whole story. There is a tag manager someone added in 2019, a chat widget that loads a second script from a different origin, a font that arrives from a CDN you have never opened, an analytics beacon that only fires on the checkout page, and a payment iframe that loads three more things.

You cannot find those by reading. You find them by watching, and report-only mode is how you watch.

Both headers at once

Once you have a policy enforced, you do not have to choose. Send both:

Content-Security-Policy: <the policy you enforce today>
Content-Security-Policy-Report-Only: <the tighter policy you want next>

The first protects users now. The second tells you what would break if you tightened it. This is how you move from a loose policy to a strict one without ever shipping an outage — and it is how you should plan every change from here on.

Where do the reports go?

Add a report-uri directive naming an endpoint that accepts a POST. The browser sends a small JSON document every time something violates the policy. That endpoint can be:

The reason to use something rather than nothing is volume. A busy site generates a lot of reports, most of them noise from browser extensions injecting scripts into your pages, and you will want them grouped and deduplicated rather than arriving as a million rows.

Start here, literally today

Content-Security-Policy-Report-Only: default-src 'self'; report-uri <your endpoint>

Deliberately strict, deliberately report-only. It will generate a lot of violations on the first day, and every one of them is information you did not have yesterday. That list is your inventory, and step 3 is about reading it.

You can do this now

Add that one header to one page of your site. Not the whole site, not a sprint of work — one header, one page. Nothing will break, because nothing can.