CSP report-only Misconfigs: Why Your XSS Still Executes
Content-Security-Policy-Report-Only teaches and monitors. It does not block. Teams paste a strict policy in report-only, watch dashboards go green, and forget to flip to enforcing Content-Security-Policy. Hunters still get JavaScript execution — and sometimes a bonus via the reporting channel itself.
If your XSS alert fires in a browser with report-only headers, the CSP did not save them.
Read the headers correctly
On a reflected or stored XSS candidate, inspect response headers:
Content-Security-Policy→ enforcingContent-Security-Policy-Report-Only→ telemetry only- Both present → enforcing policy is whatever the non-report-only header says; report-only may be a stricter draft
Do not assume a long CSP string means script is blocked. Confirm with a minimal payload that matches the real sink.
PortSwigger’s CSP guidance is the right companion here: CSP and XSS.
Common “we have CSP” failures
- Report-only forever on production marketing or app origins
- Enforcing policy with
unsafe-inlineand wildcards that allow your payload - Nonces that rotate in HTML but not in JSON/XHR-driven sinks
script-src *or CDN wildcards that host attacker JSONP- Framing protections missing while script policy looks strict
Your report should show the exact header names and values. Screenshot the network panel; quote the header in text for triagers who read diffs.
Report-only as an info leak
report-uri / report-to endpoints receive violation JSON: blocked URLs, document URI, sometimes sample code. That can expose:
- Hidden admin paths users trip while browsing
- Internal file URLs or tokens embedded in pages
- Extension behavior and obscure query parameters
Open redirects or SSRF on the reporting collector are rarer but worth a glance when the endpoint sits on the same app.
Do not flood report-uri with automated junk. One intentional violation demonstrating leakage is enough.
Hunting workflow
- Find an injection point that would be XSS without CSP.
- Check which CSP headers are present.
- Execute a harmless proof (
alerton a lab, or a DOM change) under the victim origin’s policy. - If only report-only exists, state clearly that no enforcing policy blocked script.
- Optionally trigger one violation and inspect whether the report body discloses sensitive paths.
Severity conversation
Report-only misconfig alone (no XSS) is often informational. Pair it with a working XSS and the CSP claim collapses — that combination supports impact. Pure reporting leaks depend on what data appears in violation documents.
Watch for duplicated policies where marketing pages enforce CSP but the authenticated app origin stays report-only. XSS on the app origin is what matters; do not let a strict blog header distract you.
Remediation advice that lands
Ship enforcing CSP gradually: start report-only, fix breakage, then enable enforcement on the same policy. Avoid unsafe-inline without nonces/hashes. Keep report-uri on a dedicated collector that does not reflect sensitive internals back to browsers. Document that CSP is defense-in-depth, not a substitute for output encoding — align with OWASP XSS prevention and CWE-79.
A policy that only reports is a diary, not a lock. Say that plainly in the write-up.