Troubleshooting
Solutions for common CSP configuration issues.
Reports Not Appearing
Section titled “Reports Not Appearing”Check for an Ad Blocker First
Section titled “Check for an Ad Blocker First”This is the most common cause, and the only one with no local symptom. Content blockers — uBlock Origin, Adblock Plus, Brave Shields, Privacy Badger and others — block the browser’s POST to any reporting endpoint. There is no console error, no CSP violation, and nothing in DevTools that reads as a failure to a non-expert. Reports simply never arrive, and a correctly configured site looks identical to one that has had no violations.
To confirm it:
- Open DevTools and switch to the Network tab.
- Reload the page and trigger a violation.
- Look for a request to
ingest.us.headerhawk.com. If it is missing, or shows as cancelled or blocked, an extension is the cause. - Disable the extension for your origin and reload. Reports should appear within a minute.
Check the Reporting Host Is Allowed
Section titled “Check the Reporting Host Is Allowed”A site keeps reports only from the hosts it lists. Each is an exact host
(example.com) or a wildcard (*.example.com). A site that lists only
example.com discards reports from www.example.com, app.example.com, and
every other subdomain. A wildcard matches every subdomain at any depth, but
not the bare domain: a site that lists only *.example.com discards
reports from example.com itself.
This matters most when one CSP header is deployed across several hosts, which is the normal case. The endpoint returns the same success status whether a report was stored or discarded — deliberately, so that the response cannot be used to discover which hosts a tenant monitors — so nothing in the browser shows a problem.
To check, open the site’s setup: the site’s row under Settings → Sites,
or, with the site chosen in the switcher at the top of the dashboard,
Set up this site. If reports have been discarded because their host is not
allowed, the panel lists how many and which hosts they came from. If a listed
host is one of yours, choose Allow beside it — as the host itself, or, for
a subdomain, as a wildcard over its parent, so docs.staging.example.com is
offered *.staging.example.com rather than *.example.com — and reports from
it are kept from then on. Members, admins and owners can do this. Hosts that do not share
a domain with any of the site’s hosts are counted together as
(other hosts) rather than named, since anyone can send reports claiming any
host.
Check Your Header
Section titled “Check Your Header”Verify the CSP header is being sent:
curl -I https://your-site.com | grep -i content-security-policyYou should see either Content-Security-Policy or Content-Security-Policy-Report-Only.
Verify the Report URI
Section titled “Verify the Report URI”Ensure your report-uri directive is correct:
report-uri https://your-ingest-code.ingest.us.headerhawk.com/rCommon mistakes:
- Missing
https:// - Wrong ingest code
- Typo in the domain name
- A 12-character code in the path (
/r/k3x9mp2nq7ab) rather than the host. Only the 8-character codes of sites created before ingest codes moved into the host go in the path, onhttps://ingest.us.headerhawk.com/r/{code}
Test Manually
Section titled “Test Manually”Send a test report. Use a page on one of the site’s own hosts as the
document-uri — a report whose document-uri matches none of the site’s hosts
is discarded, and the response looks identical either way:
curl -i -X POST \ https://your-ingest-code.ingest.us.headerhawk.com/r \ -H "Content-Type: application/csp-report" \ -d '{"csp-report":{"document-uri":"https://your-site.com/","violated-directive":"script-src","original-policy":"default-src self"}}'Other statuses do tell you something:
| Status | Meaning | What to do |
|---|---|---|
404 |
Nothing is accepting reports for that ingest code: either no site has it, or the site it belongs to is deactivated | Check the code against the site’s setup panel, then check the site is active |
400 |
A code in the path that is not an 8-character one (a 12-character code goes in the host), or the report body failed validation | Copy the endpoint from the setup panel, and check the body has document-uri, violated-directive and original-policy |
429 |
Rate limited | Back off |
404 covers both the unknown-code and the deactivated-site case, and does not
say which. That is deliberate, for the same reason a host-mismatch drop still
returns 204: the ingest code sits in the report-uri of every page that uses
it, so it is public by construction, and a status that distinguished “real
code” from “no such code” would let anyone work through the keyspace and
confirm which codes belong to real customers. Both of your own
cases are visible from the dashboard, where the ambiguity costs you nothing.
Browser Console
Section titled “Browser Console”Check the browser console for CSP messages. Violations appear as warnings even in report-only mode.
Script Hash Reports Not Appearing
Section titled “Script Hash Reports Not Appearing”Script integrity reporting ('report-sha256') has its own set of silences,
separate from violation reporting. This section is the diagnostic short list;
Script Integrity Monitoring explains the
mechanism in full — what the data covers, how sampling works, and how to read
the Origins, Scripts and Pages views.
It needs the Reporting API, not report-uri
Section titled “It needs the Reporting API, not report-uri”A hash report is a Reporting API report type. report-uri predates report
types and carries CSP violation reports only, so it has no way to deliver a
hash report. A policy combining 'report-sha256' with report-uri looks
correct and reports nothing.
Use the Reporting-Endpoints header with report-to:
Reporting-Endpoints: csp-endpoint="https://your-ingest-code.ingest.us.headerhawk.com/r"Content-Security-Policy-Report-Only: script-src 'self' 'report-sha256'; report-to csp-endpointNote the quotes. 'report-sha256' is a keyword source expression and must be
quoted and placed inside script-src; bare report-sha256 is parsed as a
hostname and does nothing.
A cached script produces no hash
Section titled “A cached script produces no hash”The browser generates a hash when it fetches and executes a script. On a normal reload an external script is usually served from cache, no fetch happens, and no hash is generated — however correct your policy is. A hard refresh (shift+reload) forces the fetch, which removes caching as an explanation; it is not a guarantee that a report will follow, since support is still narrow (see below).
The same effect applies to real traffic: a site with well-cached assets reports far fewer hashes than it has scripts, and returning visitors contribute almost none. Combined with the sample rate, treat the Scripts page as a sample of your scripts rather than a complete inventory.
Browser support is narrow
Section titled “Browser support is narrow”Chromium 133+ implements report-sha256; WebKit support is in progress;
Firefox does not support it. Some Chromium builds accept the keyword and emit
no hash reports at all. An empty Scripts page is not by itself evidence of a
misconfiguration — see
Browser support is narrow
for what has actually been observed.
Other Browser Reports
Section titled “Other Browser Reports”Every report type beyond CSP violations has its own header and its own ways of staying silent. Browser Report Types lists the header for each; the setup panel under Settings → Sites generates them for the types the site has switched on.
Reports arrive, but the page shows fewer
Section titled “Reports arrive, but the page shows fewer”That is sampling. Each type has a sample rate in the site’s settings, and at a rate below 100% HeaderHawk keeps that share of the reports it receives and discards the rest before they count towards your quota. Deprecations and interventions start at 5% and script hashes at 10%, because a page can send the same one on every load.
Counts are scaled back up by the rate, so a total marked ≈ or estimated stands for the reports sent, not just the ones kept. A list of individual reports shows only the kept ones, which is why DevTools can show twenty deprecation reports leaving the page and the Explorer one. Raise the rate for a type in the site’s settings to keep more of it; the quota counts what is kept.
No network error (NEL) reports
Section titled “No network error (NEL) reports”- Chromium-based browsers only. Firefox and Safari do not implement Network Error Logging, so their visitors never send one.
- It uses
Report-To, notReporting-Endpoints. NEL predatesReporting-Endpointsand still names its group through the olderReport-Toheader. The setup generates both lines; keep them together, and keep the group name inNEL’sreport_tothe same as inReport-To. - The browser caches the policy. A browser learns the NEL policy from a
response over HTTPS and keeps it for
max_ageseconds (30 days in the generated header). A header you just added takes effect only for browsers that have visited since, and one you removed stays in effect for browsers that have not. The setup adds a line withdrawing the policy (max_age: 0) while NEL is switched off and reports are still arriving. - Only failures are asked for. The generated header sets
success_fractionto0, so a site whose requests all succeed sends nothing. A failure that stops the browser reaching you at all is reported once it can reach the ingestion endpoint again, not as it happens.
No deprecation, intervention or crash reports
Section titled “No deprecation, intervention or crash reports”Browsers send these three to the endpoint named default. A
Reporting-Endpoints header naming the endpoint anything else,
headerhawk included, gets CSP and the other policy reports and none of
these. The setup names the endpoint default while any of the three is
switched on; if you wrote the header yourself, check the name. The one
exception is a crash on a page that also declares an endpoint named
crash-reporting: Chrome sends it there instead.
They are also rare by nature. A deprecation is sent only when a page calls an API the browser plans to remove, and a crash only when a tab crashes or stops responding. A quiet Browser signals section on a well-maintained site is the expected result, not a fault.
Site Breaking After Adding CSP
Section titled “Site Breaking After Adding CSP”Start with Report-Only
Section titled “Start with Report-Only”Always use Content-Security-Policy-Report-Only first:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri https://your-ingest-code.ingest.us.headerhawk.com/rThis reports violations without blocking anything.
Common Resources That Break
Section titled “Common Resources That Break”| Issue | Solution |
|---|---|
| Inline scripts blocked | Add 'unsafe-inline' or use nonces |
| CDN scripts blocked | Add the CDN domain to script-src |
| Google Fonts blocked | Add fonts.googleapis.com and fonts.gstatic.com |
| Images not loading | Add domains to img-src or use https: |
| API calls failing | Add API domain to connect-src |
Finding Blocked Resources
Section titled “Finding Blocked Resources”- Open browser DevTools
- Go to the Console tab
- Look for “Refused to…” messages
- Note the blocked URL and violated directive
Too Many Reports
Section titled “Too Many Reports”Reduce Noise
Section titled “Reduce Noise”Some violations are expected or from browser extensions. Filter in HeaderHawk dashboard by:
- Document host (your domains only)
- Blocked host (exclude known extension domains)
- Directive (focus on critical directives)
Common False Positives
Section titled “Common False Positives”| Source | Cause | Action |
|---|---|---|
chrome-extension:// |
Browser extensions | Filter out in dashboard |
moz-extension:// |
Firefox extensions | Filter out in dashboard |
about:blank |
Injected iframes | Usually safe to ignore |
localhost |
Dev tools or extensions | Filter out in dashboard |
Switching to Enforce Mode
Section titled “Switching to Enforce Mode”Pre-Switch Checklist
Section titled “Pre-Switch Checklist”- Run in report-only mode for at least a week
- Review all violation types in the dashboard
- Add legitimate resources to your policy
- Test critical user flows
- Have a rollback plan
Making the Switch
Section titled “Making the Switch”Change the header from:
Content-Security-Policy-Report-Only: ...To:
Content-Security-Policy: ...Keep the report-uri to continue receiving reports about enforced blocks.
Rollback Plan
Section titled “Rollback Plan”If things break:
- Immediately revert to
Content-Security-Policy-Report-Only - Check new violations in the dashboard
- Update policy to allow legitimate resources
- Try enforcing again
Specific Issues
Section titled “Specific Issues”Inline Scripts
Section titled “Inline Scripts”Problem: Refused to execute inline script
Solutions:
- Move to external file (recommended)
- Use nonce:
Content-Security-Policy: script-src 'nonce-abc123'<script nonce="abc123">...</script>
- Use hash:
Content-Security-Policy: script-src 'sha256-...'
- Allow unsafe-inline (not recommended):
Content-Security-Policy: script-src 'unsafe-inline'
Inline Styles
Section titled “Inline Styles”Problem: Refused to apply inline style
Solutions:
- Move styles to external CSS file
- Use
'unsafe-inline'for styles (lower risk than scripts):Content-Security-Policy: style-src 'self' 'unsafe-inline'
eval() and Function()
Section titled “eval() and Function()”Problem: Refused to evaluate a string as JavaScript
Cause: Your code or a library uses eval(), new Function(), or similar.
Solutions:
- Replace with safer alternatives
- If unavoidable, add
'unsafe-eval'(security risk):Content-Security-Policy: script-src 'unsafe-eval'
WebSocket Connections
Section titled “WebSocket Connections”Problem: WebSocket connection refused
Solution: Add to connect-src:
Content-Security-Policy: connect-src 'self' wss://your-websocket-server.comFrames and Embeds
Section titled “Frames and Embeds”Problem: Cannot load content in iframe
Solution: Add source to frame-src:
Content-Security-Policy: frame-src 'self' https://youtube.com https://player.vimeo.comGetting Help
Section titled “Getting Help”If you’re stuck:
- Check the browser console for specific error messages
- Review violations in the HeaderHawk dashboard
- Search for the specific directive and error message
- Test with a minimal policy and add directives incrementally