← All Tools

How to Roll Out CSP Without Breaking Your Site — 3 Stages From Report-Only to Enforcement

Guide · Last verified Aug 28, 2026

Content-Security-Policy (CSP) is a security header with a proven track record, but in practice the most common failure story is "the policy was misconfigured and it took the site down." Miss just one third-party widget or one inline style, and that whole resource gets blocked outright. That's why the standard approach with CSP isn't to flip on enforcement mode immediately — it's to go through an observation-only stage first and narrow the policy down gradually.

1. Why turning on enforcement mode first is risky

The Content-Security-Policy header immediately blocks any resource load that violates the policy. The problem is that, in a lot of real production sites, even the developers themselves don't fully know every script, stylesheet, image, and font their site loads and from where. Hidden dependencies lurk in places you wouldn't notice — an ad script dynamically pulling a sub-resource from yet another domain, or a CMS editor auto-inserting inline style attributes. If you enforce the policy from the start, every one of these hidden dependencies that gets caught can break the page or silently stop a feature from working — and the worst possible order of discovery is having users find and report it first.

2. Stage 1: observe only, with Report-Only

The standard way to avoid this problem is the Content-Security-Policy-Report-Only header. True to its name, it never blocks a resource for violating the policy — it only collects the fact that a violation happened, as a report. When the browser detects a violation, it sends a JSON violation report to the endpoint specified in the policy, either report-uri (the older mechanism) or report-to (the newer one).

Example stage-1 header: Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' https://cdn.example.com; report-uri /csp-report
In this state, a script loaded from a domain not in the policy will still actually load and execute — but a violation report gets fired off to /csp-report. The site keeps behaving exactly as usual while you get to observe, in advance, "what would have broken if this policy had been enforced."

At this stage, running your draft policy through the CSP Validator first lets you catch structural problems — a typo in a directive name, a missing default-src — before the reports even start piling up.

3. Stage 2: analyze the reports and refine the policy

After running Report-Only for a few days to a few weeks, violation reports pile up showing exactly which resources in real traffic conflict with the policy. As you review them, they split into two branches.

If you skip this stage and jump straight to enforcement, you end up sorting these two branches after the site has actually broken, not before. That "advance sorting" is the entire reason the Report-Only stage exists.

4. Stage 3: switch to enforcement mode

Once the reports settle down and the policy looks like it matches real traffic patterns, switch the same policy string over by just changing the header name to Content-Security-Policy. From this point on, policy violations are actually blocked. It's a good idea to keep report-uri in place right after the switch too — violation reports keep coming in even under enforcement mode, and that channel stays useful as an early-warning system for catching a newly added feature that breaks the policy.

StageHeaderBehaviorPurpose
1Content-Security-Policy-Report-OnlyNo blocking, reports onlyObserve the blast radius
2(same, iterated repeatedly)No blockingRefine the policy from the reports
3Content-Security-PolicyActually blocks on violationEnforcement, with reports still collected

5. data: URIs carry completely different risk levels depending on the directive

One judgment call you'll run into repeatedly while refining a policy is whether to allow a data: URI. Even though it's the same data: scheme, the risk swings widely depending on which directive you put it in.

Pasting a policy into the CSP Validator uniformly flags usage of unsafe-inline, unsafe-eval, and wildcards (*) regardless of which directive they're in — but it doesn't automatically make the directive-specific contextual judgment that "data: is safe in this directive but risky in that one." When drafting a policy, it's safest to use the tool's warning list as a first-pass filter and check which directive each data: URI sits in yourself, using the criteria above.

Frequently Asked Questions

Q. Is it fine to leave the Report-Only header on forever and never switch to enforcement mode?

No, that provides no security benefit. Report-Only is purely for observation and never actually blocks anything, so you have to eventually switch to Content-Security-Policy for CSP to serve any real defensive purpose.

Q. Can I send the Report-Only header and the enforcement header at the same time?

Yes, you can send both headers simultaneously. It's common to run them in parallel — enforcing an already-stabilized, strict policy via Content-Security-Policy, while separately observing an even stricter experimental policy you're working toward next via Report-Only.

Q. Should I use report-uri or report-to?

report-uri is older but has broader browser compatibility; report-to is based on the newer Reporting API and is more flexible, but requires a separate Report-To header configuration. If compatibility matters most, keep report-uri; on a modern stack, it's common to specify both to have a fallback.

Q. How do I validate a draft policy?

Pasting your header value into the CSP Validator parses it directive by directive and flags unsafe keywords, wildcards, and a missing default-src. You can use it to catch structural typos or omissions before deploying with Report-Only.