A Content Security Policy report shows a blocked inline script — diagnose the client-side flaw
Your search page runs under a nonce-based Content Security Policy (CSP). Overnight this violation report starts arriving repeatedly, always with the same document URI shape.
Constraints:
- the page renders the search term client-side into the results header
- the server-rendered HTML for this route contains no inline script tags
"csp-report":
document-uri = https://shop.example.com/search?q=...
referrer =
violated-directive = script-src-elem
blocked-uri = inline
status-code = 200
script-sample =
Diagnose the cause and state the fix.
An inline script without a nonce reached the DOM although this route ships none, so it was injected client-side — DOM XSS. The search term flows from the URL into the results header through a parsing sink. CSP blocked and reported it; fix the sink, not the policy.
- ✗Dismissing violation reports as extension noise without checking the route
- ✗Silencing the report with
unsafe-inlineinstead of fixing the sink - ✗Assuming an inline violation implies server-rendered markup
- →Which source in this flow do you trace first, and to which sink?
- →Why does an empty
script-samplefield not rule out an injected payload?
What the report says
violated-directive = script-src-elem
blocked-uri = inline
status-code = 200
blocked-uri = inline means an inline script with no nonce ended up in the DOM. But by the constraints this route's server-rendered HTML has no inline scripts — so the tag appeared in the browser. The empty referrer plus the repeat pattern on one document-uri (/search?q=...) points at direct hits on prepared links.
Diagnosis
This is DOM XSS: a value from location.search reaches the results header through a parsing sink.
// vulnerable: the string is parsed as markup
const q = new URLSearchParams(location.search).get('q');
header.innerHTML = 'Results for: ' + q;
CSP did its defense-in-depth job — it blocked execution and reported it — but the hole itself is still there.
The fix
const q = new URLSearchParams(location.search).get('q');
header.textContent = 'Results for: ' + q;
✅ Fix the sink, not the policy. ⚠️ Adding unsafe-inline would remove the reports and the last barrier at the same time.