Understanding CSP Report-Only Mode
Content Security Policy (CSP) is a powerful security mechanism, but it can also break website functionality if not properly configured. CSP report-only mode allows you to test and refine your CSP rules before enforcing them, preventing unintended content blocking.
CSP has two operational modes: enforcement mode (Content-Security-Policy header) and report-only mode (Content-Security-Policy-Report-Only header). Report-only mode monitors policy violations and reports them without actually blocking resources.
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.
Comparison: Enforcement vs Report-Only
Enforcement Mode (Content-Security-Policy):
- Blocks resources that violate the policy
- Users experience content loss if policy is too strict
- Risk of breaking website functionality
- Most restrictive
Report-Only Mode (Content-Security-Policy-Report-Only):
- Allows all resources
- Reports violations
- No user-facing impact
- Safe for testing
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
}
}
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
});
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://yourname.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;
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;
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;
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
- Start permissive: Begin with lenient policies to identify what needs whitelisting
- Monitor consistently: Regularly review violation reports
- Use reporting service: Don't handle raw reports; use dedicated services
- Set time limits: Plan specific periods for testing (e.g., 2 weeks)
- Iterate gradually: Tighten policies incrementally
- Document decisions: Explain why each domain is whitelisted
- Test cross-browser: Violations may vary by browser
- 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.

