Featured image of post Testing CSP Rules Safely with Content-Security-Policy-Report-Only Featured image of post Testing CSP Rules Safely with Content-Security-Policy-Report-Only

Testing CSP Rules Safely with Content-Security-Policy-Report-Only

Avoid live site regressions by routing script violations through CSP reports before enforcing lockouts.

The Broken-Widget Problem

Deploying a Content Security Policy is one of the strongest defenses against XSS attacks. But a single misconfigured directive can silently break inline scripts, block CDN resources, or disable analytics. If you apply CSP directly via the Content-Security-Policy header and a critical script gets blocked, your production site breaks—often without an obvious error console notification.

This is where Report-Only mode saves the day.

Report-Only vs Enforced Mode

CSP supports two headers:

HeaderBehavior
Content-Security-PolicyBlocks violations immediately
Content-Security-Policy-Report-OnlyLogs violations without blocking

With Report-Only, a disallowed script still executes but the browser POSTs a JSON violation report to a designated endpoint. You can audit every false positive before locking the policy down.

Setting Up Report-Only

Content-Security-Policy-Report-Only: default-src 'self';
  script-src 'self' https://analytics.example.com;
  report-uri https://your-site.example/csp-reports;
  report-to csp-endpoint;

The report-uri directive (deprecated but widely supported) sends reports to a URL. The newer report-to directive uses the Report-To header to define an endpoint group. For maximum browser coverage, include both.

Receiving Reports

Each violation report is a JSON payload:

{
  "csp-report": {
    "document-uri": "https://example.com/page",
    "blocked-uri": "https://evil.com/script.js",
    "violated-directive": "script-src 'self'",
    "effective-directive": "script-src",
    "original-policy": "default-src 'self'; script-src 'self'"
  }
}

You can collect these via:

  • A dedicated server route that logs to a file or database
  • Third-party services like Report URI (report-uri.com)
  • Cloud provider log analytics (AWS S3 + Lambda, Cloudflare Workers)

Gradual CSP Rollout Strategy

A safe rollout follows four phases:

Phase 1 — Monitor (Report-Only)

Apply Content-Security-Policy-Report-Only to a subset of traffic using a backend toggle or a reverse-proxy rule. Let it run for 1–2 weeks to capture all legitimate resource patterns.

# nginx: report-only for staging
add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-reports;" always;

Phase 2 — Triage Reports

Analyze the collected reports. Common false positives include:

  • Inline event handlers (onclick, onload) that need 'unsafe-hashes' or 'nonce-...'
  • Third-party CDN scripts not listed in script-src
  • WebSocket connections not covered by connect-src

Phase 3 — Lock Down (Enforced)

Once the report stream stabilizes (zero unexpected violations for several days), switch to the enforced header. Keep Report-Only active with a stricter policy to explore further tightening.

Phase 4 — Iterate

Content-Security-Policy: default-src 'self';
  script-src 'self' 'nonce-abc123';
  report-uri https://your-site.example/csp-reports;

Content-Security-Policy-Report-Only: default-src 'self';
  script-src 'none';
  report-uri https://your-site.example/csp-reports;

Run a second Report-Only policy alongside the enforced one to test an even stricter future version without risk.

Key Directives for Report-Only

DirectivePurpose
report-uriURL to receive violation reports (legacy)
report-toEndpoint group name from Report-To header
script-srcControls script execution
style-srcControls stylesheet loading
img-srcControls image sources
connect-srcControls fetch/XMLHttpRequest/WebSocket
frame-ancestorsControls iframe embedding

Challenges with Report-Only

  • Report-only fires once per violation per page load. After the first report, subsequent identical violations on the same page are silently ignored by most browsers.
  • Some browsers send reports without a body. Filter out empty reports early.
  • Report-Only cannot catch form submission target violations (form-action). You need enforced mode to block those.

Summary

Content-Security-Policy-Report-Only is an essential tool for rolling out CSP without risk. It lets you observe violations in production, iterate on your policy based on real traffic, and only enforce when you are confident nothing will break. Pair it with a report-collection service, run it for at least a week, and graduate to enforced mode gradually.