How to Check Your Content Security Policy (and Read It)
Five practical ways to check a site's Content-Security-Policy: DevTools, curl, Report-Only mode, evaluators and a scanner. Plus what unsafe-inline, wildcards and missing directives really mean.
Check your own site while you read
Free report in 1–3 minutes. No account needed.
Results in 1–3 minutes · Free · No signup required

You added a Content-Security-Policy header, or you inherited one. Now the real questions: is it actually being sent, is the browser enforcing it, is it blocking things it should not, and does it protect against anything at all? A policy that is present but full of 'unsafe-inline' and * passes a "header exists" test and still gives an attacker everything they need.
This guide covers five ways to check a CSP, how to read what you find, and how to roll out a policy safely using Report-Only mode. If you only want a quick pass/fail on a live site, use our CSP checker; the rest of this post explains what to do with the result. For what CSP is and how to fix specific findings, see the Content-Security-Policy check page.
Method 1: Browser DevTools
DevTools is the most direct check because it shows what the browser actually received and enforced.
Read the header
- Open the page, then DevTools (F12 or Cmd+Option+I) and the Network tab.
- Reload the page and click the first request, the HTML document (type "document").
- In Headers, scroll to Response Headers and look for
content-security-policyorcontent-security-policy-report-only.
Check the document request, not images or scripts. CSP is evaluated per document, so the header on the HTML response is the one that counts. If your site serves different HTML for logged-in pages, check those too.
Watch for violations
Open the Console tab. When a resource is blocked, the browser logs a message along the lines of "Refused to load the script ... because it violates the following Content Security Policy directive: script-src ...". The message names the blocked URL and the directive that blocked it, which is usually enough to decide whether to allow the source or remove the resource.
In Report-Only mode the same violations are logged as reports, but nothing is blocked.
Catch policies set in HTML
A CSP can also be delivered with a <meta http-equiv="Content-Security-Policy"> tag. Search the Elements tab for it. Meta policies have limits: frame-ancestors, report-uri and sandbox are ignored there, and Report-Only cannot be set through a meta tag. Prefer the HTTP header.
Method 2: curl
For a scriptable check, or to see exactly what a server sends without browser extensions interfering:
curl -sI https://example.com | grep -i content-security-policy
The -I flag sends a HEAD request. Some servers answer HEAD differently from GET, or only add security headers on certain routes, so if the output is empty, confirm with a GET and discard the body:
curl -s -o /dev/null -D - https://example.com | grep -i content-security-policy
Add -L to follow redirects, and check the final destination: a policy on the redirecting http:// response says nothing about the page the user ends up on. To make the long value readable, split it by directive:
curl -sI https://example.com | grep -i '^content-security-policy:' | tr ';' '\n'
Two things to watch for. First, a response can carry more than one CSP header, or one header with comma-separated policies. The browser enforces all of them, and a resource must satisfy every policy, so the effective result is the intersection, not the first one. Second, CDNs and reverse proxies sometimes add or strip headers, so always test the public URL rather than the origin server.
Method 3: Report-Only mode and report endpoints
Static checks tell you what the policy says. They cannot tell you what it would break on real pages, in real browsers, with real third-party scripts. That is what Content-Security-Policy-Report-Only is for: the browser evaluates the policy, reports violations, and blocks nothing.
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' https://cdn.example.com; report-to csp-endpoint; report-uri /csp-report
Reporting-Endpoints: csp-endpoint="https://example.com/csp-report"
There are two reporting mechanisms. report-uri is the older directive, deprecated in the spec but still widely supported, and it sends a JSON body with content type application/csp-report. report-to is the newer one: it names an endpoint defined in the Reporting-Endpoints header (or the older Report-To header) and posts reports as application/reports+json. Sending both is common practice so that browsers supporting only one still report.
You can run a normal enforced policy and a Report-Only policy at the same time, which lets you test a stricter version against live traffic while the current one stays in force.
You do not need to build a collector to start. Hosted CSP reporting services exist, and a simple route that logs the JSON body to your existing logs is enough for a small site. Expect noise: browser extensions inject scripts and styles that trigger violations on your page. Filter reports whose blocked-uri or source file starts with chrome-extension://, moz-extension:// or similar before you act on them.
Method 4: CSP evaluators
An evaluator parses the policy text and flags weaknesses without needing to see your pages. Google's CSP Evaluator is the best-known example; paste in a policy and it reports high-severity issues such as 'unsafe-inline' without a nonce, wildcard sources, missing object-src and base-uri, and allowlisted hosts that are known to host JSONP endpoints or Angular libraries that can bypass the policy.
Evaluators are good at answering "is this policy meaningfully strict?" They are not a substitute for Report-Only testing, because they cannot know which legitimate resources your pages load.
Method 5: Scan the live site
A scanner fetches your real URL, reads the header the way a browser would, and grades it alongside everything else around it: HSTS, cookie flags, TLS and the other headers. That matters because CSP rarely fails alone. The same misconfigured deploy that dropped your CSP often dropped other headers too.
Run the CSP checker on your URL to see the policy, which directives are weak, and what to change. A full SecScanner scan takes one to three minutes, and the same checks can run daily with monitoring so a deploy that silently removes the header is caught the next day instead of the next audit.
How to read the result: common findings
Whichever method you use, you end up with a policy string. This table covers the findings you will meet most often.
| Finding | What it means | Fix |
|---|---|---|
'unsafe-inline' in script-src |
Any inline script runs, including one injected by an attacker. This removes most of CSP's XSS protection. | Move scripts to files, or use nonces or hashes. Note that browsers ignore 'unsafe-inline' when a nonce or hash is also present in the same directive. |
'unsafe-eval' |
Allows eval(), new Function() and string-based setTimeout. Turns string-to-code bugs into code execution. |
Remove the eval usage; some libraries and dev-mode bundlers need it, so do not ship dev settings to production. |
No default-src |
Any fetch directive you did not list has no restriction at all. | Start from default-src 'self' and loosen per directive. |
No object-src |
Plugin content (<object>, <embed>) falls back to default-src, or is unrestricted if that is missing too. |
Add object-src 'none'. |
No base-uri |
An injected <base> tag can redirect every relative script URL to an attacker's server. base-uri does not fall back to default-src. |
Add base-uri 'self' or 'none'. |
No frame-ancestors |
Any site can embed yours in a frame (clickjacking). It does not fall back to default-src. |
Add frame-ancestors 'self' or 'none'. |
Wildcard source (*, https:, *.cdn.com) |
Scripts may load from any host matching the pattern. If an attacker can host a file there, the policy does not stop them. | List exact hosts, or use nonce plus 'strict-dynamic' so host lists stop mattering. |
data: in script-src |
Lets attackers run scripts from a data URL. | Remove it. data: is generally acceptable in img-src. |
Only Report-Only header present |
Nothing is enforced. Violations are reported and the page works as if there were no policy. | Fine as a stage; promote to enforcing once reports are clean. |
Nonces, hashes and strict-dynamic
These three are the modern way to allow inline scripts without allowing all of them.
- Nonce: a random value generated for each response, placed in the header (
script-src 'nonce-R4nd0m') and in the tag (<script nonce="R4nd0m">). It must be unpredictable and different on every response; a fixed nonce is just a password everyone can read. - Hash: the base64 SHA-256/384/512 digest of an inline script's exact contents (
'sha256-...'). It suits static inline snippets that never change; any edit breaks the hash. - 'strict-dynamic': trusts scripts loaded by an already-trusted (nonced or hashed) script, and makes browsers that support it ignore host allowlists and
'self'in that directive. It is what lets a tag manager load further scripts without listing every host.
A common strict policy pattern is:
script-src 'nonce-R4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'
Roll out a policy step by step
Do not paste an enforcing policy into production on day one. This sequence works:
- Inventory. Check what the site loads today: scripts, styles, fonts, images, frames, API calls, analytics, chat widgets.
- Start in Report-Only. Send
Content-Security-Policy-Report-Onlywith a conservative policy and a reporting endpoint. - Read reports for several days. Cover a full traffic cycle, including rarely used pages such as checkout, settings and error pages. Each legitimate violation is either a source to allow or a resource to clean up.
- Remove inline code. Move inline scripts to files or add nonces. Do this before anything else; it is the step that turns CSP into real XSS protection.
- Add the cheap wins early.
object-src 'none',base-uri 'self'andframe-ancestorsrarely break anything. - Switch to enforcing. Rename the header to
Content-Security-Policy. Keep a Report-Only copy of any stricter next version running alongside. - Keep checking. New third-party scripts will start getting blocked. Re-scan after deploys, or schedule it.
Server examples
nginx
add_header Content-Security-Policy-Report-Only "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; report-uri /csp-report" always;
The always parameter adds the header to error responses too. Remember that add_header in a nested block replaces inherited headers from the outer block rather than adding to them, a classic way to lose a header on one route.
Apache
Header always set Content-Security-Policy-Report-Only "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; report-uri /csp-report"
This needs mod_headers enabled.
Next.js
A static policy can go in next.config.ts via headers(). A nonce-based policy has to be generated per request, so it belongs in middleware (middleware.ts, or proxy.ts in newer versions): generate a nonce, put it in the CSP header, and pass it on a request header so your layout can read it and apply it to scripts. Pages that use a nonce must be rendered dynamically, since a statically cached HTML page would carry the same nonce for everyone. The step-by-step setup, including the edge cases, is in our Next.js security headers guide.
Common mistakes when testing
- Checking only the homepage. Headers set per route or per framework layout can differ on other pages.
- Trusting a green tick. "CSP present" is not "CSP effective". Read the directives.
- Testing logged out only. Authenticated pages load different scripts and are often the ones worth protecting.
- Judging by extensions' noise. Test in a clean browser profile.
- Forgetting the meta tag limits. A policy in a meta tag cannot do
frame-ancestorsor reporting. - Setting the header on the origin but not the CDN. Always test the public URL.
Where to go next
Run the CSP checker against your site to get a baseline, then work through the findings with the CSP check page for remediation steps. CSP is one layer of a wider set of response headers; the HTTP security headers guide covers the others and how they fit together, and the XSS prevention guide explains the attacks a strict policy is meant to contain.
Related Articles
Check Your Website Security
Want to see how your website measures up? Run a free security scan with SecScanner to identify vulnerabilities and get actionable remediation guidance.
Scan Your Website Free