Web Security

What is CSP report-only mode?

Learn how to use Content Security Policy report-only mode to test and validate CSP rules without blocking content, minimizing user impact during implementation.

By Inventive HQ Team

CSP report-only mode is a testing mode for Content Security Policy that logs policy violations without blocking anything. You enable it by sending the Content-Security-Policy-Report-Only response header instead of the enforcing Content-Security-Policy header. The browser evaluates every script, style, image, and connection against your policy exactly as it would in enforcement mode — but when a resource would be blocked, the browser lets it load and instead POSTs you a JSON violation report. The result: you get a complete list of everything your policy would break, gathered from real users, before it breaks anything for real. Once the reports are clean, you switch to the enforcing header and the same policy starts actually protecting the page.

That is the summary an AI overview will give you. Here is what it can't: report-only mode is not a security setting — it provides zero protection — and the reporting directive most guides still show you (report-uri) is deprecated. Below is how the mode actually works, a side-by-side of the two headers, and the modern reporting setup that keeps working in current browsers.

The report-only rollout: log, tune, enforce

The whole point of report-only mode is to turn a risky "flip the switch and hope" deployment into a safe, evidence-driven loop. You start with a policy in report-only mode, watch the violations roll in, tighten the policy until the reports are clean, and only then enforce it.

The CSP report-only rollout A three-stage pipeline: violations are logged but not blocked, the policy is tuned, then it is enforced so violations are blocked. A marker moves left to right through the stages. Report-Only: log, tune, then enforce 1. Report-Only Violation detected Resource still loads Report sent 2. Tune policy Whitelist real dependencies Remove noise until reports are clean 3. Enforce Violation detected Resource BLOCKED Real protection now active One policy, moved from observe to protect once the evidence says it is safe

How Report-Only Mode Works

When you use report-only mode, the browser doesn't block any resources that violate the policy. Instead, the browser reports the violations through the reporting mechanism you've configured.

The main use case is testing: you can deploy a CSP in report-only mode, monitor violations, refine the policy, and only switch to enforcement when confident the policy won't break functionality.

Content-Security-Policy vs Content-Security-Policy-Report-Only

Both headers use identical policy syntax. The only difference is what the browser does when a resource violates the policy — and that difference is everything.

AspectContent-Security-Policy (enforce)Content-Security-Policy-Report-Only (test)
Response header nameContent-Security-PolicyContent-Security-Policy-Report-Only
Violating resourceBlocked — does not load or executeAllowed — loads and runs normally
Sends violation reportsYes (if a reporting directive is set)Yes (if a reporting directive is set)
User-facing impactPage can break if policy is too strictNone — nothing changes for the user
Security protectionYes — actually stops XSS / injectionNo — monitoring only, zero protection
disposition field in report"enforce""report"
Requires a reporting endpointOptional (blocking works without one)Effectively required (reports are the whole point)
Primary useProduction defenseSafe rollout, testing, tightening a live policy

The takeaway: report-only is for learning what a policy does; enforcement is for doing what a policy promises. A policy that lives forever in report-only mode looks secure in your config but protects nothing.

Implementing Report-Only Mode

Setting the Header

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

In various server technologies:

Express.js:

const helmet = require('helmet');
const express = require('express');
const app = express();

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'unsafe-inline'"],
    styleSrc: ["'self'"],
    imgSrc: ["'self'", "https:"],
    reportUri: "/csp-report"
  },
  reportOnly: true  // Report-only mode
}));

Nginx:

add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' https:; report-uri /csp-report;";

Apache:

Header add Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' https:; report-uri /csp-report;"

Setting Up Violation Reporting

Report-only mode is most valuable when you capture and analyze violation reports.

Basic Reporting with report-uri

Specify where violations should be reported:

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

When a violation occurs, the browser POSTs a JSON report to /csp-report:

{
  "csp-report": {
    "document-uri": "https://example.com/page",
    "violated-directive": "script-src",
    "effective-directive": "script-src",
    "original-policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /csp-report",
    "disposition": "report",
    "blocked-uri": "https://cdn.example.com/analytics.js",
    "status-code": 200
  }
}
Advertisement

Handling CSP Reports

Express.js endpoint:

app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
  const violation = req.body['csp-report'];

  console.log('CSP Violation Report:', {
    documentUri: violation['document-uri'],
    violatedDirective: violation['violated-directive'],
    blockedUri: violation['blocked-uri']
  });

  // Store violations in database for analysis
  CSPViolation.create({
    documentUri: violation['document-uri'],
    violatedDirective: violation['violated-directive'],
    blockedUri: violation['blocked-uri'],
    originalPolicy: violation['original-policy'],
    timestamp: new Date()
  });

  res.status(204).send(); // No content response
});

Modern Reporting: report-to and Reporting-Endpoints

Important: the report-uri directive shown above is deprecated. CSP Level 3 replaces it with the report-to directive, which does not take a URL directly — it references a named endpoint that you define separately in the Reporting-Endpoints HTTP response header.

Reporting-Endpoints: csp-endpoint="https://example.com/csp-report"

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self';
  report-to csp-endpoint

The catch is browser support: browsers that understand report-to ignore report-uri, but some browsers still only understand report-uri. The pragmatic answer is to send both directives so every browser can report:

Reporting-Endpoints: csp-endpoint="https://example.com/csp-report"

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

Note that an older Report-To header (JSON group syntax) also existed to define endpoints; it is itself being superseded by the simpler Reporting-Endpoints header shown here. One more wrinkle: reports delivered via report-to use the newer Reporting API JSON shape (a body object), which differs slightly from the legacy csp-report payload — your endpoint should be prepared to parse both.

Build and Preview a Policy

You don't have to hand-write these directives from scratch. Use the generator below to assemble a policy, then deploy it under the Content-Security-Policy-Report-Only header first before enforcing it.

Loading interactive tool...

Third-Party Reporting Services

Rather than implementing your own reporting, use services like:

  • Report-uri.com: Dedicated CSP reporting service
  • Sentry: Error tracking with CSP support
  • Bugsnag: Crash reporting with CSP reporting

Report-uri.com example:

Content-Security-Policy-Report-Only:
  default-src 'self';
  report-uri https://report-uri.com/r/d/csp/reportOnly

Testing Workflow

Implementing CSP effectively follows a structured approach:

Step 1: Deploy Lenient Report-Only Policy

Start with a lenient policy in report-only mode:

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' 'unsafe-inline' https:;
  style-src 'self' 'unsafe-inline' https:;
  report-uri /csp-report

This policy is permissive but still identifies major issues.

Step 2: Monitor Violations

Collect violation reports for a period (days or weeks depending on traffic):

// Count violations by type
SELECT violated_directive, COUNT(*) as count
FROM csp_violations
GROUP BY violated_directive
ORDER BY count DESC;

// Identify problematic resources
SELECT blocked_uri, COUNT(*) as count
FROM csp_violations
WHERE violated_directive = 'script-src'
GROUP BY blocked_uri
ORDER BY count DESC;

Step 3: Whitelist Necessary Resources

Based on violation reports, add trusted resources to your policy:

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' https://cdn.example.com https://analytics.google.com/analytics/web/;
  style-src 'self' https://fonts.googleapis.com;
  report-uri /csp-report

Step 4: Tighten the Policy

Remove overly permissive directives:

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' https://cdn.example.com https://analytics.google.com/analytics/web/;
  style-src 'self' https://fonts.googleapis.com;
  img-src 'self' https:;
  report-uri /csp-report

Step 5: Switch to Enforcement

Once violations are resolved, switch to enforcement mode:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example.com https://analytics.google.com/analytics/web/;
  style-src 'self' https://fonts.googleapis.com;
  img-src 'self' https:;
  report-uri /csp-report

Note: Use both headers during transition for additional safety.

Running Both Headers Simultaneously

Best practice during transition is running both headers:

Content-Security-Policy: [enforcement policy]
Content-Security-Policy-Report-Only: [stricter policy for testing]

This approach:

  • Enforces your current policy
  • Tests a stricter policy in report-only mode
  • Identifies issues before enforcement
  • Allows gradual policy tightening

Example transition:

# Current enforced policy (permissive)
Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'unsafe-inline' https:

# Testing stricter policy (report-only)
Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' https://trusted-cdn.com;
  report-uri /csp-report

Users experience the enforced policy, while the stricter policy is tested safely.

Common Report-Only Patterns

Initial Exploration

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

Very permissive, identifies major issues.

Testing Stricter Version

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  style-src 'self' https://fonts.googleapis.com;
  report-uri /csp-report

More restrictive, tests specific resource whitelisting.

Testing No Inline Scripts

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

Very strict, identifies inline script dependencies.

Analyzing Violation Reports

Common Violations and Solutions

Inline Scripts:

{
  "violated-directive": "script-src",
  "blocked-uri": "inline"
}

Solution: Remove inline scripts or use nonces/hashes:

<script nonce="random-nonce">
  console.log('Allowed');
</script>

Third-Party Analytics:

{
  "violated-directive": "script-src",
  "blocked-uri": "https://analytics.google.com/analytics.js"
}

Solution: Whitelist analytics domain:

script-src 'self' https://analytics.google.com

Google Fonts:

{
  "violated-directive": "font-src",
  "blocked-uri": "https://fonts.gstatic.com/s/..."
}

Solution: Add font-src directive:

font-src 'self' https://fonts.gstatic.com

Best Practices for Report-Only Mode

  1. Start permissive: Begin with lenient policies to identify what needs whitelisting
  2. Monitor consistently: Regularly review violation reports
  3. Use reporting service: Don't handle raw reports; use dedicated services
  4. Set time limits: Plan specific periods for testing (e.g., 2 weeks)
  5. Iterate gradually: Tighten policies incrementally
  6. Document decisions: Explain why each domain is whitelisted
  7. Test cross-browser: Violations may vary by browser
  8. Archive reports: Keep historical violation data for analysis

Production Recommendations

Once in production:

  • Monitor violation reports continuously
  • Alert on unexpected new violations (potential attacks)
  • Maintain both enforcement and report-only during testing phases
  • Have a response plan for CSP violation patterns
  • Regularly review and tighten CSP

Limitations of Report-Only Mode

  • Doesn't provide actual protection (only monitoring)
  • Report delivery isn't guaranteed (can be blocked by network)
  • Some browsers have CSP report limitations
  • Report-only adds header size overhead
  • Requires active monitoring to be valuable

Report-only mode is an essential tool for safely implementing Content Security Policy. By using it to test policies before enforcement, you can significantly reduce the risk of accidentally breaking website functionality while still improving security. The key is active monitoring and iterative refinement based on violation reports.

Frequently Asked Questions

What is CSP report-only mode?

CSP report-only mode is a testing mode for Content Security Policy that monitors and reports policy violations without blocking anything. You enable it with the Content-Security-Policy-Report-Only response header instead of the enforcing Content-Security-Policy header. The browser evaluates every resource against your policy, and when something would be blocked it sends you a JSON violation report — but the resource still loads normally. This lets you see exactly what a policy would break before it actually breaks it for real users.

What is the difference between Content-Security-Policy and Content-Security-Policy-Report-Only?

They use identical policy syntax but behave oppositely. Content-Security-Policy enforces the policy: any resource that violates it is blocked, so a too-strict policy visibly breaks the page. Content-Security-Policy-Report-Only never blocks anything — it only sends violation reports to your reporting endpoint. Enforcement protects users; report-only observes. The standard rollout is to run report-only first until the reports are clean, then swap the header name to switch on enforcement.

Does CSP report-only mode actually protect my site?

No. Report-only mode provides zero protection — it does not block XSS, injected scripts, clickjacking, or any other attack. It is purely a monitoring and diagnostic tool for building and validating a policy. Because it never blocks a malicious resource, a policy left in report-only mode gives you visibility but no defense. Real protection only begins when you deploy the enforcing Content-Security-Policy header.

Can I run both CSP headers at the same time?

Yes, and it is a recommended pattern during a policy migration. Send an enforcing Content-Security-Policy header with your current known-good policy and a Content-Security-Policy-Report-Only header with a stricter candidate policy. Users are protected by the enforced policy right now, while the browser reports what the tighter policy would break — so you can tighten safely without a risky flag day.

Is report-uri deprecated in CSP?

Yes. The report-uri directive is deprecated in favor of the report-to directive (CSP Level 3), which names an endpoint defined separately in the Reporting-Endpoints HTTP response header. However, browser support for report-to is still incomplete, and browsers that support report-to ignore report-uri. The common practice is to send both directives so that older browsers use report-uri and newer ones use report-to.

How do I set up CSP violation reporting?

Add a reporting destination to your policy and stand up an endpoint to receive the JSON reports. For broad compatibility, include both report-uri /csp-report (legacy) and a report-to directive backed by a Reporting-Endpoints header. Your endpoint accepts an HTTP POST with a JSON body describing the violated directive, the blocked URI, and the document URI, then logs or stores it. Many teams skip building this and use a hosted service such as report-uri.com or Sentry instead.

How long should I keep a CSP policy in report-only mode?

Long enough to capture the real traffic and content patterns your site actually produces — typically one to four weeks depending on traffic volume, release cadence, and how many third-party scripts you run. You want to see reports from real users across browsers, devices, and A/B-tested code paths before you trust the policy. Watch for the report volume to flatten out and the remaining violations to be ones you understand and have decided to allow; that is the signal it is safe to enforce.

Why is my CSP report-only policy not sending reports?

The most common causes are a missing or misspelled reporting directive (report-uri or report-to with no matching Reporting-Endpoints header), a reporting endpoint that returns an error or is itself blocked by the policy, or simply no violations occurring because the policy is too permissive. Report delivery is also best-effort — browsers may batch, sample, or drop reports, and network filtering or ad blockers can suppress them — so a low report count is not proof that your policy is clean.

CSPcontent security policyreport-onlysecurity testing