CSP Analyzer / policy evaluator

Paste mode makes no request URL mode works 166 bypass endpoints

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 1

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.

⌘Ctrl+⏎ to run
A nonce plus '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.

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 token object-src is read as a host source of script-src, and object-src is never set at all.
  • Report-only delivery. Content-Security-Policy-Report-Only reports 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-uri and sandbox are 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 misses object-src or base-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.