Enabling Content Security Policy on a live website can break functionality: analytics scripts, widgets, and inline scripts may be blocked. Report-Only mode lets you test a policy without blocking anything—the browser only sends reports about violations.
You can check your current CSP and debug your policy with the CSP Analyzer on rechecker.ru.
How Report-Only works
The Content-Security-Policy-Report-Only header works just like Content-Security-Policy, but it does not block resources when violations occur—it only sends a report to the specified endpoint. This lets you:
- See all the resources loaded by the website
- Identify inline scripts and styles
- Compile a list of third-party domains
- Configure the policy before enabling blocking
Syntax
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-reportOr use the Report API:
Content-Security-Policy-Report-Only: default-src 'self'; report-to csp-endpoint
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"/csp-report"}]}report-uri vs report-to
| Parameter | report-uri | report-to |
|---|---|---|
| Status | Being deprecated | Recommended |
| Format | POST to a URL | Report API |
| Support | Broad | Modern browsers |
| Grouping | No | Yes (endpoint group) |
For maximum compatibility, you can specify both:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report; report-to csp-endpoint
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"/csp-report"}]}Report format (report-uri)
A POST request to report-uri with this body:
{
"csp-report": {
"document-uri": "https://example.com/page",
"referrer": "https://example.com/",
"violated-directive": "script-src",
"effective-directive": "script-src",
"blocked-uri": "https://evil.com/malicious.js",
"original-policy": "default-src 'self'; script-src 'self'",
"disposition": "report",
"status-code": 200,
"source-file": "https://example.com/page",
"line-number": 15,
"column-number": 10
}
}Content-Type: application/csp-report
Processing reports on the server
Node.js (Express)
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
const report = req.body['csp-report'];
if (report) {
console.log('CSP violation:', {
document: report['document-uri'],
directive: report['violated-directive'],
blocked: report['blocked-uri'],
line: report['line-number'],
column: report['column-number']
});
// Сохранить в БД, отправить в Sentry и т.д.
}
res.status(204).end();
});Nginx
Nginx does not process the POST body for CSP reports. You need a backend (Node, PHP, Python) or a service such as report-uri.com.
Logging to a file
app.post('/csp-report', express.raw({ type: 'application/csp-report' }), (req, res) => {
fs.appendFileSync('csp-reports.log', JSON.stringify(req.body) + '\n');
res.status(204).end();
});Testing strategy
Stage 1: Strict policy in Report-Only
Enable the strictest policy you want to achieve:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; report-uri /csp-reportCollect reports for 1–2 weeks. You will get a complete list of violations.
Stage 2: Analyze the reports
Group them by:
violated-directive— which directive was violatedblocked-uri— which sources need to be addeddocument-uri— which pages have issues
Stage 3: Gradually tighten the policy
Add legitimate sources to the policy. For inline scripts, decide whether to:
- Move them to an external file
- Add a nonce
- Calculate a hash (for static content)
Stage 4: Switch to blocking mode
When reports become infrequent or come only from bots/extensions, replace it with Content-Security-Policy.
Using both headers at the same time
You can use both: a blocking policy and Report-Only for monitoring:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.com
Content-Security-Policy-Report-Only: default-src 'none'; report-uri /csp-reportA stricter Report-Only policy will show potential violations that the current policy still allows.
Sentry integration
Sentry supports CSP reports. Set report-uri to a Sentry endpoint or proxy the reports:
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
const report = req.body['csp-report'];
if (report) {
Sentry.captureMessage('CSP violation', {
level: 'warning',
extra: report
});
}
res.status(204).end();
});Common violations on first use
- Inline scripts — Google Analytics, plugins, ads
- Third-party CDNs — jQuery, Bootstrap, fonts
- data: URIs — inline images, base64
- eval() — some older libraries
- blob: — generating files in the browser
Recommendations
- Do not leave Report-Only enabled indefinitely without processing the reports—they will accumulate
- Filter out noise (bots, extensions that inject content)
- Use Report-Only before each policy change that makes it stricter
- For a complete CSP guide, see Content Security Policy: Complete configuration guide