Reference Guide

How to Use the Content-Security-Policy-Report-Only Header

The CSP Report-Only header tests a policy without blocking content. Use it to collect violation reports, refine required sources, and reduce breakage before enforcement.

Generate a CSP
Deployment Checklist

CSP report-only deployment and validation checklist

Use this sequence to turn a candidate header and its CSP violation reports into reviewable evidence. A quiet collector is not a pass unless reporting itself and the required application states were tested.

  1. Define the pages, roles, consent states, actions, and supported browsers in the test matrix.
  2. Capture the exact final response headers, including duplicate report-only or enforced policies added by the application, proxy, CDN, or host.
  3. Connect a bounded collector you control, version the candidate policy, and trigger one deliberate violation to prove the reporting path works.
  4. Exercise representative success, failure, login, checkout, upload, and third-party integration flows; reproduce each relevant signal before changing policy.
  5. Classify each signal, make the narrowest justified code or directive change, and retest both required flows and negative controls in a clean browser profile.
  6. Move to bounded enforcement only when the tested coverage is documented, required-flow violations are understood, and rollback is ready; keep monitoring after release.
Example

Example Content-Security-Policy-Report-Only header

This report-only CSP header tests a policy without blocking scripts, images, frames, or API connections that violate it.

Controlled, Reproducible Case Study

Trace one inline-script violation from detection to correction

Scope: This is a controlled teaching lab, not a customer result. Run it only on a staging origin you control. The same-origin /csp-report path represents a bounded collector you operate; Script Sentinel does not host that endpoint.

Observed lab result: In a clean headless Chrome 152.0.7977.64 loopback run, the starting statement executed and produced both a matching violation event and collector POST. After the same statement moved to /static/lab.js under the unchanged header, it executed without that matching inline event or POST during a 500 ms post-load observation within a 2.5-second virtual-time run. This validates the minimal fixture, not other browsers or application flows.

  1. Configure the staging server to send the lab response header below on /lab.html, save the starting HTML as that file, and make POST /csp-report return a small success response after bounded processing.
  2. Open /lab.html in a clean browser profile. Lab script ran confirms that the statement executed; that page result alone does not prove the browser received the header or emitted a report.
  3. Confirm a matching console message, securitypolicyviolation event, or collector request with the field values documented below. Execution plus that matching signal shows the report-only policy observed the statement without blocking it.
  4. Verify that the inline code is owned and required, replace /lab.html with the corrected HTML, save the moved statement as /static/lab.js, and repeat the test. Then temporarily restore the starting /lab.html; the matching inline signal must return before you count the quiet corrected pass as meaningful.
Lab Response Header

Header used for both lab passes

Keep this policy unchanged between the starting and corrected files so the retest isolates the code change. The legacy report-uri keeps this small lab easy to inspect; use the compatibility guidance below for production reporting.

Starting /lab.html

Deliberate inline-script file

script-src 'self' excludes inline script. The report-only disposition still lets this test statement run.

Corrected /lab.html

Reference a same-origin script

Move the statement into /static/lab.js. The existing 'self' source permits the external script without adding 'unsafe-inline'.

Corrected /static/lab.js

Moved script statement

Violation Triage

How the observed violation becomes a policy decision

For this starting file, use only the matching observation: disposition = report, effectiveDirective / effective-directive = script-src-elem, blockedURL / blockedURI / blocked-uri = inline, and originalPolicy / original-policy matching the exact policy value in the lab response header.

Page result Lab script ran is the execution control only. Pair it with the matching violation signal before concluding that report-only observed the statement. A separate enforced policy can still block content.
disposition: report The matching signal must say report; this ties it to monitoring rather than enforcement. Never infer disposition from whether the feature appeared to work.
effectiveDirective / effective-directive The value should be script-src-elem. The inline element is governed by script-src through CSP fallback because this candidate has no separate script-src-elem.
blockedURL / blockedURI / blocked-uri The value should be inline. It identifies the kind of resource the policy would reject; it is evidence to locate code, not a reason to allow every inline script.
originalPolicy / original-policy It should match default-src 'self'; script-src 'self'; report-uri /csp-report;. Delayed reports from an older candidate must not justify a current change.
Classification to correction to retest After verifying the code is owned and required, externalize the identical statement to same-origin /static/lab.js. The corrected page should still say Lab script ran without that matching inline signal; temporarily restore the starting HTML and confirm the signal returns.

This closes one reproduced finding only. If externalizing is impractical, evaluate a per-response nonce or an exact hash. Do not add 'unsafe-inline' reflexively, and do not treat this lab as proof that untested routes, states, or browsers are ready for enforcement.

Modern Violation Report

Modern Reporting API payload (application/reports+json)

The report-to directive uses the Reporting API. The browser sends a JSON array and can batch multiple reports in one request. CSP entries use type: "csp-violation" and camelCase body fields.

Legacy Violation Report

Legacy report-uri payload (application/csp-report)

The deprecated report-uri directive sends one JSON object with a top-level csp-report envelope and kebab-case field names. Collectors may still need this format for older browser behavior.

blockedURL / blocked-uri The source the browser would have blocked under the tested policy.
effectiveDirective / effective-directive The directive that controlled the resource after CSP fallback behavior.
documentURL / document-uri The document where the violation was observed.
sourceFile / source-file The page, script, or stylesheet that triggered the blocked load when the browser can identify it.
lineNumber / line-number The reported line for the attempted load, useful when tracking inline scripts or injected code.
disposition report means report-only. enforce means the browser blocked the resource.
Reporting Endpoints

Reporting-Endpoints, report-to, and report-uri example

For a compatibility period, define a named Reporting API endpoint with Reporting-Endpoints, use report-to, and include deprecated report-uri in the same policy for older reporting behavior.

Compatibility

Why send both report-to and report-uri?

report-to is the modern mechanism and is intended to replace report-uri, but older browser versions may only use the legacy directive. When both appear, browsers that use report-to ignore report-uri; legacy implementations can still post to the fallback URL.

Test the browser versions you support. Reporting can be delayed, batched, disabled, or dropped, so the absence of reports is not proof that a policy is ready to enforce.

Collector Input

Accept both report media types

  • application/reports+json contains an array that may hold several report types; process each supported csp-violation entry independently.
  • application/csp-report contains one legacy csp-report envelope.
  • Route parsing by the request Content-Type, reject malformed or oversized bodies, and return a small success response after bounded processing.
Collector Security

Treat every CSP report as attacker-controlled input

Warning: Every field can be forged, oversized, or crafted to attack logs, storage, and dashboards. Never render report values as trusted HTML or use them directly in queries, filenames, alerts, or log formats.

This is design guidance for a collector you operate. Script Sentinel does not currently provide a hosted public CSP violation collector.

Validation

Sanitize before storage or display

  • Allow only expected methods and media types, cap request bytes and batch length, and bound nesting, field counts, and string lengths.
  • Parse into a narrow schema, ignore unsupported report types and unknown fields, and reject malformed JSON without echoing it.
  • Normalize permitted URL and directive fields, encode values for their output context, and keep raw payloads out of application logs.
  • Treat sample, script-sample, URLs, referrers, user-agent text, and policy text as untrusted content.
Abuse and Noise

Rate-limit and deduplicate reports

  • Use bounded per-client and global request budgets, plus backpressure or dropping rules for traffic spikes.
  • Apply limits to both HTTP requests and the number of reports inside a modern batch.
  • Deduplicate with a server-computed fingerprint over sanitized fields such as document origin or path, effective directive, blocked origin, disposition, and a time bucket.
  • Store aggregate counts with first-seen and last-seen timestamps instead of creating an unlimited row or alert for every duplicate.
Data Governance

Privacy and retention

Report URLs, referrers, source locations, policy text, and code samples can expose account paths, query tokens, personal data, or application details. Collect only what the review workflow needs.

  • Strip URL user information, query strings, and fragments unless a documented use case requires a safer derived value.
  • Redact or omit code samples and other high-risk fields before storage, logging, analytics, or alert delivery.
  • Restrict access to stored reports, define a short retention window, aggregate older data when useful, and delete expired raw records.
  • Document the collector, processors, retention period, and deletion path in the applicable privacy notice.

For protocol details, consult the W3C Content Security Policy reporting specification and Reporting API specification.

Troubleshooting

Why am I not receiving useful CSP reports?

Debug the delivery path, active headers, browser behavior, collector response, and exercised application state separately. A quiet collector is not proof that an enforced policy is ready.

No request reaches the collector Confirm the final document response contains the report-only policy, the report-to group exactly matches Reporting-Endpoints, the endpoint is HTTPS, and a deliberate violation actually occurs.
The browser still blocks content Look for a second enforced Content-Security-Policy header or meta policy. Report-only does not disable or override an enforced policy already present.
sample is empty Code samples are not guaranteed and may be omitted or truncated. Diagnose with the effective directive, blocked URL, document URL, source file, disposition, and a reproduction path.
One violation floods storage Repeated components, retries, and multi-page flows can emit the same finding many times. Sanitize first, then aggregate a bounded fingerprint with counts and first/last-seen times.
Unknown browser-extension origins Extensions can inject scripts or styles that are not part of the application. Separate extension-shaped noise from origins reproduced in a clean browser profile before changing policy.
Reports appear only after consent or login Analytics, payments, chat, and authenticated application code may load only after a state transition. Test representative consent choices, sessions, routes, and user actions.

Reporting API delivery can be delayed, batched, rate-limited, disabled, or dropped. During the compatibility period, accept both report media types and use deprecated report-uri alongside report-to when your browser support requirements justify it.

Limit

What report-only CSP cannot protect against

  • It does not block malicious or unexpected scripts.
  • It does not replace real enforcement after validation.
  • It depends on useful CSP violation report collection and review.
  • It can be noisy if the candidate policy is too far from real behavior.
Rollout

Evidence gate before enforcement

  • The documented test matrix covers required pages, roles, states, actions, and supported browsers.
  • Every accepted source is tied to verified application code, configuration, ownership, or maintained dependency documentation.
  • A deliberate negative control still produces a report after the final candidate is deployed.
  • Required flows succeed, remaining violations are understood, and the initial enforcement scope has a tested rollback.
  • No fixed number of quiet hours proves readiness; missing, delayed, batched, or dropped reports are possible.

CSP Report-Only Header FAQ

Does report-only CSP block anything?

No. Report-only CSP evaluates the policy and reports violations, but the browser continues loading the page.

When should I move from report-only to enforcement?

Move to enforcement when violations are reviewed, required sources are accounted for, and the policy no longer blocks expected user flows.

Should report-only and enforced headers be identical?

They usually test the same resource and navigation restrictions, but report-only is not a complete simulation of enforcement. The sandbox and upgrade-insecure-requests directives have no effect in report-only, and a report-only policy must be delivered in an HTTP response header rather than a meta element. Review the final enforced header separately and introduce it with a bounded rollback plan.

Why use both report-to and report-uri?

report-to uses the modern Reporting API and application/reports+json, while deprecated report-uri uses application/csp-report and supports older reporting behavior. During a compatibility transition, send Reporting-Endpoints, include both directives, and make the collector accept both payload formats.

Can I trust CSP violation report data?

No. Treat every field as attacker-controlled. Enforce request and field limits, sanitize before storage, escape before display, rate-limit and deduplicate submissions, redact sensitive URL and sample data, and apply a documented retention period.

Next Step

Generate a policy before report-only rollout

Run a Chrome-based CSP scanner first so your report-only header starts from observed resource behavior instead of a blank policy.

Run CSP Scanner