CSP Analyzer / policy evaluator
Analyse and grade a Content-Security-Policy
Most policies in the wild do not stop XSS. Google's measurement of over a billion hosts found 94.7% of allowlist-based policies were trivially bypassable, usually via a JSONP endpoint or an AngularJS copy on a host the policy already trusts. This tool tests for exactly that: it resolves the CSP 3 fallback chain, checks every allowlisted host against 166 known bypass endpoints, catches the misspellings and missing semicolons that void a policy silently, and then writes you a strict replacement. Two ways in — paste a policy (header, meta tag, nginx or Apache line), or enter a URL and have it retrieved.
The policy
step 1http:// or https:// only. Test the page behind login too — the policy on the marketing homepage is frequently not the policy on the application.
How URL mode works, and what it costs. A browser cannot read another origin's response headers — that is the same-origin policy, not a limitation of this tool — so the request is made by a service on your behalf and the policy is handed back.
With no endpoint set, a public API sees every URL you submit and returns headers only. With your own endpoint, nothing third-party sees your targets and the page body is available too, so a minimal policy can be derived from what the page actually loads. Internal and staging hosts cannot be reached either way — use paste mode, which makes no request at all.
Only test hosts you are authorized to assess.
Run your own endpoint — Cloudflare Worker source
The same endpoint the URL Scanner uses, and it is
shared between the two tools — set it in either and both use it. Free
tier covers 100,000 requests a day. Deploy at
workers.cloudflare.com
and paste the resulting *.workers.dev URL above.
Source: worker/scanner-worker.js.
It refuses loopback, RFC 1918, carrier-grade NAT, link-local and cloud metadata addresses. Without that check the endpoint is an SSRF proxy pointed at your own infrastructure.
Accepts a bare policy, a full
Content-Security-Policy: header line, a
Content-Security-Policy-Report-Only: line, a
<meta http-equiv> tag, an nginx add_header
line or an Apache Header set line — paste whatever you have
in front of you. Multiple comma-separated policies are split and each is
analysed separately.
'strict-dynamic' is recognised properly: the host and
scheme sources that follow it are ignored by CSP 3 browsers, so they are
reported as the backwards-compatibility fallback they are rather than as
weaknesses. Google's four-directive recommendation grades
Good here, and Strong once
frame-ancestors and form-action are added — the
two it leaves out because it addresses XSS only, not clickjacking or form
hijacking.
Paste a policy above, then press Analyze.
Findings
Effective directives
fallback chain resolvedWhat the browser actually applies. A directive you never wrote
may still be in force through default-src — and four of
them never fall back at all, so leaving those out means no restriction.
| Directive | Effective value | Source | What it controls |
|---|
Recommended policy
strict rewriteThe policy above rewritten to a nonce-based strict CSP, keeping
your non-script directives. Replace {RANDOM} with a fresh
base64 value of at least 128 bits per response, generated
server-side, and put the same value in the nonce attribute of
every legitimate <script> tag. A reused or predictable
nonce is no better than 'unsafe-inline'.
Roll it out as
Content-Security-Policy-Report-Only first, collect reports for a
release cycle, then switch the header name. Report-only blocks nothing, so it
cannot break the site while you find what you missed.
Deploy it
pick your stackThe recommended policy above, as a configuration line for your platform. Where the policy needs a nonce, the generated snippet includes how to produce one — that part cannot be copy-pasted blindly, because a nonce that does not change per response is worthless.
Derived from the page itself
what it actually loadsRead from the returned HTML: the origins the page pulls resources from, and how much inline content it has. Useful as a starting allowlist — but it reflects one response only, so it will miss anything loaded by script at runtime or on other routes.
Reference
The questions this tool exists to answer.
What actually makes a CSP strong?
A per-response nonce with 'strict-dynamic', plus
object-src 'none' and base-uri 'none'. Host allowlists are
not a defence. Google measured policies across more than a billion hosts and found
94.7% of allowlist-based policies were bypassable — usually because
a trusted CDN also hosts a JSONP endpoint or a copy of AngularJS.
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
require-trusted-types-for 'script'
The https: 'unsafe-inline' at the end looks alarming and is not.
A CSP 3 browser sees 'strict-dynamic' and discards both; a CSP 1 or 2
browser ignores 'strict-dynamic' and falls back to them, which is
weaker but better than nothing. Source:
Google's strict CSP guide.
How is an allowlisted host turned into a bypass?
Two ways, both requiring nothing but the host already being trusted.
JSONP. An endpoint that reflects a callback parameter into executable position hands you arbitrary script from a trusted origin:
<!-- policy: script-src 'self' https://accounts.google.com --> <script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1)"></script>
AngularJS. If the host serves AngularJS, a template expression in injected markup is evaluated by the framework itself, so the payload never needs to be a script tag:
<!-- policy: script-src 'unsafe-eval' https://ajax.googleapis.com -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.js"></script>
<div ng-app ng-csp>{{constructor.constructor('alert(1)')()}}</div>
This analyser holds 123 JSONP endpoints, 41 AngularJS paths and 2 Flash objects,
matched against your allowlist including wildcard subdomains and path pinning. The
AngularJS class additionally needs 'unsafe-eval', so it is reported at a
lower severity when that keyword is absent.
Why would a policy be ignored entirely?
Four causes, all silent — the browser does not warn the operator, and a policy that is present in the response can still be doing nothing.
- A misspelled directive.
frame-ancestor,script-scr,default-src-elem— unknown directive names are dropped without error, so the protection was never applied. - A missing semicolon. In
script-src 'self' object-src 'none', the tokenobject-srcis read as a host source ofscript-src, andobject-srcis never set at all. - Report-only delivery.
Content-Security-Policy-Report-Onlyreports violations and blocks nothing. This is correct for a rollout and useless as a control. - Directive not valid in a meta tag.
frame-ancestors,report-uriandsandboxare ignored when the policy is delivered by<meta>.
All four are detected here. Nothing in this class shows up in a manual read of the policy, which is why they survive into production.
Which directives do not fall back to default-src?
base-uri, form-action, frame-ancestors,
report-to, sandbox and
require-trusted-types-for. If you do not write them, they are
unrestricted — a tight default-src 'self' does not
cover them.
That is why default-src 'self' alone is not a complete policy:
markup injection can still add a <base> tag, retarget a form to
an attacker's collector, or frame the page for a clickjacking attack. The effective
directive table above lists these explicitly as unrestricted when they are
missing, rather than leaving you to remember which ones inherit.
How do the verdict levels map to severity?
- Bypassable — a working XSS bypass exists.
'unsafe-inline'without a nonce, a wildcard in a script directive, or a known JSONP endpoint on the allowlist. Report the CSP as providing no XSS mitigation. - Weak — no direct bypass listed, but the policy leans on an
allowlist, or permits
'unsafe-eval', or missesobject-srcorbase-uri. - Moderate — nonce or hash based with gaps in the supporting directives.
- Good — nonce or hash based, supporting directives set, no
'strict-dynamic', so it depends on the allowlist for dynamically inserted script. - Strong — nonce with
'strict-dynamic',object-src 'none',base-uri 'none', nothing unsafe.
Every finding carries its own severity and the reasoning is shown in full, so you can disagree with the summary verdict and still use the findings.
Can I analyse a policy from an internal or staging host?
Yes — that is what paste mode is for. Public CSP checkers must
reach the host themselves, so they are useless for
staging.internal.corp, for anything behind a VPN, or for a host that
only answers with the right Host header.
Run curl -I from wherever you have access, or copy the value out of
Burp, DevTools or your nginx config, and paste it. Everything is analysed in your
browser: no request is made and nothing is uploaded.
What is Trusted Types, and should I set it?
require-trusted-types-for 'script' stops the DOM sink half of the
problem. A nonce prevents an attacker's own script tag from running; it does
nothing about your own trusted script passing attacker-controlled data into
innerHTML, eval or document.write. Trusted
Types makes those sinks reject plain strings, so a DOM XSS becomes a type error at
the sink rather than execution.
It requires application work — every legitimate sink needs a policy — so treat it as the step after a strict CSP is in place, not a prerequisite. Chromium-based browsers support it; Firefox and Safari do not yet, so it is defence in depth rather than a control you can rely on universally.