Response header
< Content-Security-Policy-Report-Only: default-src 'self'; report-to csp-endpointCSP Report-Only Header: Roll Out CSP Safely
Content-Security-Policy-Report-Only tests a policy without blocking. Rollout steps, report-uri vs report-to, sample violation JSON, and cutting noise.
- Direction
- Response
- Category
- Security
- Spec
- W3C CSP Level 3
On this page
TL;DR:
Content-Security-Policy-Report-Onlyruns a policy in observation mode: violations are reported to your endpoint, nothing is blocked. Ship it first, tune the policy against real traffic, then rename the header toContent-Security-Policy.
Rollout workflow
- Write the strict candidate policy (usually nonce-based, see Content-Security-Policy).
- Deploy it as Report-Only next to your existing policy, if any.
- Collect reports for a full release cycle, including rare pages (checkout, admin, embeds, error pages).
- Fix the first-party sources of violations: inline handlers, a missing CDN origin,
eval. Add third parties deliberately, not wholesale. - Reports drop to background noise: enforce, keep a Report-Only copy of the next stricter step.
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-r4nd0m'; object-src 'none'; base-uri 'none'; report-to csp-endpoint
Reporting-Endpoints: csp-endpoint="https://reports.example.com/csp"
Caveats from the spec: Report-Only must be delivered as an HTTP header, not a <meta> element, and violations are still logged to the DevTools console without any reporting directive. A sandbox directive has no effect in Report-Only.
report-uri versus report-to
The old mechanism, report-uri, is deprecated but still the most compatible:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-reports
The reporting-API mechanism, report-to, names an endpoint declared in Reporting-Endpoints:
Reporting-Endpoints: csp-endpoint="https://reports.example.com/csp"
Content-Security-Policy-Report-Only: default-src 'self'; report-to csp-endpoint
During migration send both directives. Browsers that support report-to ignore report-uri; the rest use it. Your collector then has to accept both formats.
What a report looks like
report-uri, POST with Content-Type: application/csp-report:
{
"csp-report": {
"document-uri": "https://example.com/signup",
"referrer": "",
"blocked-uri": "https://cdn.evil.example/lib.js",
"effective-directive": "script-src-elem",
"violated-directive": "script-src-elem",
"original-policy": "default-src 'self'; report-uri /csp-reports",
"disposition": "report",
"status-code": 200
}
}
report-to, POST with Content-Type: application/reports+json, an array of report objects. Field names are camelCase and the payload sits in body:
[
{
"type": "csp-violation",
"age": 3,
"url": "https://example.com/signup",
"user_agent": "Mozilla/5.0 ...",
"body": {
"documentURL": "https://example.com/signup",
"blockedURL": "https://cdn.evil.example/lib.js",
"effectiveDirective": "script-src-elem",
"originalPolicy": "default-src 'self'; report-to csp-endpoint",
"disposition": "report",
"statusCode": 200,
"referrer": "",
"sample": ""
}
}
]
disposition is report for Report-Only and enforce for the enforcing header. The sample field carries the first 40 characters of an inline script or style, but only when the policy includes the 'report-sample' keyword. Cross-origin blocked-uri values are truncated to scheme, host and port.
Keeping noise manageable
- Treat reports as untrusted input. Anyone can POST to your endpoint, and fields such as
script-sampleare attacker-controlled; MDN says to sanitize before storing or rendering. - Drop
blocked-urivalues withchrome-extension,moz-extensionorsafari-extensionschemes and reports whosesource-fileis an extension. They are the largest source of junk. - Deduplicate on (effective directive, blocked host, document path) and count, rather than storing every report.
- Send the endpoint reports from a separate host or path with rate limiting so a hot page cannot knock it over. Hosted collectors exist, but check what URLs and user agents you are handing them.
- Add
'report-sample'temporarily when you cannot work out which inline script is violating; remove it after because it increases data exposure.
Verify
curl -sI https://example.com | grep -i -E 'content-security-policy|reporting-endpoints'
curl -s -X POST https://reports.example.com/csp \
-H 'Content-Type: application/csp-report' \
-d '{"csp-report":{"blocked-uri":"inline","effective-directive":"script-src"}}' -i
The CSP evaluator is useful for checking the candidate policy before it ships.
Related
Frequently asked questions
What does Content-Security-Policy-Report-Only do?
The browser evaluates the policy and reports violations, but blocks nothing. Pages behave exactly as before, so you can learn what a real policy would break across real users, then switch the header name to Content-Security-Policy to enforce it.
Can I use Report-Only and enforcing CSP together?
Yes. Send both headers: the enforcing one with your current safe policy, and the Report-Only one with the stricter candidate. Violations of each are reported with a disposition of enforce or report so you can tell them apart.
Should I use report-uri or report-to?
report-uri is deprecated but has the widest support, and report-to with a Reporting-Endpoints header is its replacement. MDN advises sending both during the transition: browsers that support report-to ignore report-uri, and the others fall back to it. The two use different body formats, application/csp-report versus application/reports+json.
Why is my Report-Only policy not sending any reports?
A Report-Only policy needs a reporting directive (report-uri or report-to); without one the violation shows only in the DevTools console. Also check that the endpoint is HTTPS, that the name in report-to matches a key in Reporting-Endpoints, and that the header is on the HTTP response, since Report-Only is not supported in a meta tag.
Why am I flooded with CSP reports from browser extensions?
Extensions inject scripts and styles that violate your policy, and they often show up with blocked URIs such as chrome-extension://, moz-extension:// or inline. Filter those schemes server-side, drop reports whose source file is not your origin, and sample or deduplicate before storing.
Sources
Related
Reporting-Endpoints Header: Reporting API Setup
Reporting-Endpoints names the URLs where browsers send CSP, COOP, deprecation and crash reports. Syntax, the default endpoint, and replacing Report-To.
Content-Security-Policy Header: Directives, Nonces and Console Errors
Content-Security-Policy (CSP) limits what a page may load or run. Directives, nonces, strict-dynamic, Report-Only rollout, console errors, helmet and Next.js.
Access-Control-Allow-Credentials Header
Learn how Access-Control-Allow-Credentials controls whether browsers expose responses when credentials (cookies, auth headers) are included in CORS requests.
Access-Control-Allow-Headers Header
Learn how Access-Control-Allow-Headers specifies which custom HTTP headers can be used during cross-origin requests in CORS preflight responses.