Content-Security-Policy evaluator
Paste a Content-Security-Policy and read it the way a browser does: which directive wins, what falls back to what, and which sources quietly undo the protection.
Paste-in tool. Your browser cannot fetch another site's headers for a web page (CORS
forbids it), and WebPeeks has no server to do it, so nothing you paste leaves your browser.
Paste the policy itself, the whole header line, or the page's
<meta http-equiv="Content-Security-Policy"> tag. On Windows use
curl.exe -sI https://example.com/ | findstr /i content-security.
curl -sI https://example.com/ | grep -i content-security-policy 1 to fix · 2 warnings · 2 passed · 3 notes
- Failscript-src: 'unsafe-inline' allows inline script
Any injected <script> block or onclick= attribute runs, which is exactly what CSP exists to prevent. Replace it with nonces or hashes.
- Warnobject-src is 'self' (from default-src)
Recommended value is 'none'. It costs nothing on a modern site.
- WarnNo base-uri
base-uri does not fall back to default-src. Without it, an injected <base href> tag can redirect every relative script URL on the page to an attacker's host, bypassing nonce-based policies. Set base-uri 'none' or 'self'.
- Infoscript-src: host allowlist (2 hosts)
Allowlists are often bypassable: if any listed host serves JSONP endpoints, user uploads or an old copy of AngularJS, an attacker can run script from it. Nonces with 'strict-dynamic' avoid the problem.
- InfoNo form-action
form-action does not fall back to default-src, so an injected form could post to any host. form-action 'self' is a cheap addition if all forms submit to your own site.
- Infostyle-src allows 'unsafe-inline'
Very common and much lower risk than inline script, but injected CSS can still exfiltrate some data through attribute selectors. Worth removing when practical.
- Passframe-ancestors 'none'
Controls who may frame the page; this takes precedence over X-Frame-Options.
- PassViolation reporting is configured
Reports show what the policy blocks in real traffic, which is how to tighten it safely.
Directives as written
| Directive | Sources |
|---|---|
| default-src | 'self' |
| script-src | 'self' 'unsafe-inline' https://cdn.example.net https://analytics.example.org |
| style-src | 'self' 'unsafe-inline' |
| img-src | * data: |
| connect-src | 'self' https://api.example.com |
| frame-ancestors | 'none' |
| report-uri | /csp-report |
What each resource type actually gets
| Resource | Allowed sources | Where it comes from |
|---|---|---|
| script-src | 'self' 'unsafe-inline' https://cdn.example.net https://analytics.example.org | set directly |
| script-src-elem | 'self' 'unsafe-inline' https://cdn.example.net https://analytics.example.org | from script-src |
| script-src-attr | 'self' 'unsafe-inline' https://cdn.example.net https://analytics.example.org | from script-src |
| style-src | 'self' 'unsafe-inline' | set directly |
| img-src | * data: | set directly |
| font-src | 'self' | from default-src |
| connect-src | 'self' https://api.example.com | set directly |
| media-src | 'self' | from default-src |
| object-src | 'self' | from default-src |
| frame-src | 'self' | from default-src |
| worker-src | 'self' 'unsafe-inline' https://cdn.example.net https://analytics.example.org | from script-src |
| manifest-src | 'self' | from default-src |
| base-uri | not restricted | — |
| form-action | not restricted | — |
| frame-ancestors | 'none' | set directly |
How a browser reads a policy
The rules come from the W3C's Content Security Policy Level 3 specification. A policy is a list of directives separated by semicolons. Each directive is a name followed by a list of sources, separated by spaces. Three rules catch people out:
- The first occurrence of a directive wins. If
script-srcappears twice, the second one is ignored entirely. Policies assembled from several config files often end up like this. - Keywords need single quotes.
'self'means "this origin";selfwithout quotes is read as a host called self and matches nothing. - Fallback is per directive.
script-src,img-src,connect-srcand the other fetch directives fall back todefault-src.frame-srcfirst trieschild-src;worker-srctrieschild-src, thenscript-src.base-uri,form-actionandframe-ancestorsdo not fall back at all.
The second table in the result applies these rules, so you can see what each resource type is actually allowed, rather than what the policy appears to say.
What is checked, and why
Most of the checks concern scripts, because stopping injected script (cross-site scripting) is the main
job of a CSP. The approach follows the research published by Google's security team (Weichselbaum and
others, CSP Is Dead, Long Live CSP!, ACM CCS 2016), which found that most real-world policies
based on host allowlists could be bypassed, and recommended nonces or hashes with
'strict-dynamic' instead.
| Pattern | Verdict | Why |
|---|---|---|
| 'unsafe-inline' | Fail (script) | Any injected <script> or onclick runs. Ignored if a nonce or hash is also present. |
| * , https: , data: | Fail (script) | Script can come from anywhere an attacker can host a file. |
| 'unsafe-eval' | Warn | Allows eval() and new Function(), widening what an injection can do. |
| object-src not 'none' | Warn | Plugin content is obsolete; 'none' costs nothing. |
| no base-uri | Warn | An injected <base> tag can repoint relative script URLs, even under a nonce policy. |
| host allowlist | Info | Works, but is bypassable if any listed host serves JSONP or user uploads. |
| nonce + 'strict-dynamic' | Pass | The recommended design; allowlists are ignored by browsers that support it. |
A worked example
The weak example above is the kind of policy many sites ship first. It has a default-src
'self', which looks restrictive, but then:
script-src 'self' 'unsafe-inline' …allows inline script with no nonce or hash, so an injected<script>block runs. This is the one serious failure.object-srcis not set, so it inherits'self'from default-src. Better than nothing, but'none'is the recommendation.- There is no
base-uriand noform-action, and neither falls back to default-src. img-src *allows images from anywhere. That is common and not a script risk, so it is not flagged, but it does mean an injected image tag can send data to any server in its URL.
Press Strict example to compare a policy built the recommended way: a per-response nonce,
'strict-dynamic', object-src 'none' and base-uri 'none'. Its
https: and 'unsafe-inline' are there only for very old browsers and are
reported as ignored rather than as failures.
Moving from an allowlist to nonces
- Generate a fresh random nonce (at least 128 bits, base64) for every response, in the server or framework, never a fixed value.
- Add
nonce="…"to every<script>tag the page itself writes, including the loader snippets for analytics or tag managers. - Move inline event handlers (
onclick="…") into script files; nonces do not cover them. - Ship
script-src 'nonce-…' 'strict-dynamic'; object-src 'none'; base-uri 'none'as Report-Only first, read the reports, then enforce it.
Limits of a static check
This evaluator reads text. It cannot know whether a host you allow serves a JSONP endpoint, whether your
nonce is really random per response, or whether a script you trust with 'strict-dynamic'
will load something unsafe. It evaluates the first policy only, where a header carries several
separated by commas (browsers enforce all of them together). And it does not know which browsers your
visitors use: directives such as require-trusted-types-for are enforced in Chromium-based
browsers only.
Common questions
What is the difference between a nonce and a hash?
A nonce is a random value the server generates for every response and puts both in the CSP header and on each <script nonce="..."> tag; an attacker cannot guess it, so injected scripts lack it. A hash is the base64 SHA-256 (or 384, 512) of a specific inline script's exact text; it suits static pages where the inline script never changes. Nonces need a server that renders each page; hashes work on static hosting.
Why does my policy list https: and 'unsafe-inline' next to a nonce?
That is the backward-compatible form of a strict policy. Browsers that understand 'strict-dynamic' ignore https: and 'unsafe-inline' once a nonce or hash is present, so modern browsers get the strict behaviour, while very old browsers that do not understand nonces fall back to something that at least does not break the site.
Can I test a policy without breaking my site?
Yes. Send it as Content-Security-Policy-Report-Only with a report-uri or report-to endpoint. Browsers apply nothing, but report every resource the policy would have blocked. Watch the reports for a week or two, adjust, then switch the header name to Content-Security-Policy.
Does default-src cover base-uri, form-action and frame-ancestors?
No. default-src is the fallback only for fetch directives such as script-src, img-src and connect-src. base-uri, form-action and frame-ancestors have no fallback, so a policy that sets only default-src 'self' leaves all three unrestricted. The "What each resource type actually gets" table above shows this for any policy you paste.
Is style-src 'unsafe-inline' a problem?
Much less than script. Injected CSS cannot run code, but it can change what a page shows and, with attribute selectors that load background images, leak some values from the page to another server. Many sites keep 'unsafe-inline' for styles because frameworks and libraries set inline styles; it is reasonable to fix script first.