CSP Checker
Paste a Content-Security-Policy and an HTML page to see what the policy blocks (inline scripts, styles, event handlers, external resources) and the exact hashes that would allow the inline parts. Nothing is fetched. Runs in your browser.
Content-Security-Policy checker (policy + HTML → what would be blocked, and the hashes to allow it)
Asked for on October 15, 2026. Who asked is never published. Request a tool
Where your data goes
| Using this page | Using the API, MCP or A2A | |
|---|---|---|
| Where it runs | In your browser. The policy and the HTML are parsed and checked by JavaScript on this page; nothing is sent anywhere, and none of the URLs in the HTML are fetched. | In a Cloudflare Pages Function at the Cloudflare data centre nearest the caller. |
| What leaves your device | Nothing you enter. After the page and its scripts load, no request carries your input. The browser enforces this: the page's Content-Security-Policy sets connect-src 'none' and form-action 'none', which block fetch, XHR, WebSocket, EventSource, beacons and form posts. | The server receives the policy and the HTML, analyses them in memory inside the request, and returns the result. Nothing is fetched: the server makes no network requests, not even for the URLs in the HTML. Your input is not stored or logged; only the per-call metadata row (operation, time, status, sizes) is recorded. For unpublished pages use the page, which does the same work in your browser and sends nothing. |
| Stored | Nothing leaves the page. No cookies, analytics or third-party scripts; the browser keeps only your View → Inline config choice. | Your inputs and results: never. What is stored is your client record (client ID, a SHA-256 hash of your secret, expiry, and the profile you registered with) and one metadata row per call: operation, time, transport, status, error code, duration, request and response sizes, and country. |
| Logged | Nothing (no requests are made). | Only the per-call metadata above, attributed to your client ID. Never parameters, bodies, results, or credentials. Cloudflare, as the host, also processes request metadata (IP address, full URL including the query string, headers) under its own policies, so put inputs in POST bodies. |
| Access | Open to anyone. | Requires a client ID and secret (how to get one). HTTPS only; TLS terminates at Cloudflare. |
Details and how to verify this yourself: Security & privacy.
User guide
The tool sits in a window that works like a desktop app: menus on the top row and the full-screen icon at its right (Esc leaves full screen). The page URL option described below is in Settings → Options…, an overlay. To keep it in view instead, choose View → Inline config; the page remembers that choice. Help → Keyboard shortcuts… lists the keys.
- Paste the policy on the left: the value of your
Content-Security-Policyheader, for exampledefault-src 'self'; script-src 'self'. Leave it empty if the HTML carries its own<meta http-equiv="Content-Security-Policy">. - Paste the HTML on the right: the page as your server sends it (view source, or a file from your build output), not what the browser’s inspector shows after scripts have run. Sample loads a page with something blocked of each kind.
- Read the verdict and the Findings: every inline script,
<style>,style=""attribute, event handler (onclick=…),javascript:link and external resource, blocked ones first. Each says which directive applied, and either the source that allowed it or why it is blocked and how to fix it. - Hashes to add lists, per directive, the exact
'sha256-…'sources that would allow everything inline that is blocked, ready to copy into your policy. Attributes also need'unsafe-hashes', which is included when it applies. - Under each Policy you see its directives, which directive governs each kind of resource after fallback, and warnings about weaknesses (
'unsafe-inline', wildcards, noobject-src 'none', nobase-uri, sources missing their quotes).
With only a policy, the page is a policy linter: it parses it, resolves the fallbacks and lists its warnings.
Page URL (Settings). 'self' means “the page’s own origin”, so give the URL the page is served from to check absolute URLs against 'self' correctly. Without it, relative URLs count as same-origin and absolute ones as cross-origin.
For agents. csp_check over the API, MCP or A2A takes policy, html and url and returns the same findings. A good use: after building a site with a strict policy, check each built page before deploying.
Specification
The checker implements the parts of Content Security Policy Level 3 that can be decided from the policy and the HTML alone.
Parsing. A header value may hold several policies separated by commas; each is enforced, so something is allowed only if every policy allows it (CSP3 §4.1). Within a policy, directives are separated by ;, names are case-insensitive, and a repeated directive is ignored after its first occurrence (§2.2.1). A <meta http-equiv="Content-Security-Policy"> policy is enforced too, but only for the elements after it, and it cannot set frame-ancestors, report-uri or sandbox (§3.3).
Fallbacks (§6.8.3). Each request or inline item has an effective directive; the first of its fallback list that the policy contains applies, and if none does it is unrestricted:
| Effective directive | Falls back to |
|---|---|
script-src-elem | script-src, default-src |
script-src-attr | script-src, default-src |
style-src-elem | style-src, default-src |
style-src-attr | style-src, default-src |
worker-src | child-src, script-src, default-src |
frame-src | child-src, default-src |
img-src, font-src, media-src, connect-src, manifest-src, object-src | default-src |
base-uri, form-action, frame-ancestors | nothing: unrestricted unless set |
What is checked, and against what.
| Finding | From | Directive |
|---|---|---|
inline-script | <script> without src whose type is JavaScript, module, importmap or speculationrules | script-src-elem |
inline-style | <style> | style-src-elem |
style-attribute | any style="" attribute | style-src-attr |
event-handler | any on…="" attribute | script-src-attr |
javascript-url | javascript: in a/area href, iframe/frame src, form action, formaction | script-src-elem |
external-script | <script src>, <link rel=modulepreload>, <link rel=preload as=script> | script-src-elem (worker-src for as=worker) |
external-style | <link rel=stylesheet>, <link rel=preload as=style> | style-src-elem |
image | <img src> and each srcset URL, <source srcset>, <input type=image>, <video poster>, <link rel=icon> | img-src |
font, connect, media, manifest | <link rel=preload as=font / fetch / audio / video / track>, <audio>, <video>, <source src>, <track>, <link rel=manifest> | font-src, connect-src, media-src, manifest-src |
frame, object | <iframe src>, <frame src>; <object data>, <embed src> | frame-src; object-src |
form-action, base-uri | <form action>, formaction; <base href> | form-action; base-uri |
<script> elements with any other type (application/json, application/ld+json, text/template, …) are data blocks: browsers never run them and CSP never checks them (HTML “prepare the script element”). They are counted, not reported.
Inline content (§6.7.3.3). In order:
'unsafe-inline'allows it, unless the same list has a hash or a nonce, or (for scripts)'strict-dynamic'; then'unsafe-inline'is ignored.- For
<script>and<style>elements, anonceattribute equal to a'nonce-…'source allows it, if the element is nonceable (no attribute name or value contains<scriptor<style, the HTML spec’s dangling-markup check). - A
'sha256-…','sha384-…'or'sha512-…'source equal to the hash of the content allows it. Forstyleand event-handler attributes this applies only when the list also has'unsafe-hashes'. Hash values may be base64 or base64url.
javascript: URLs are allowed only by 'unsafe-inline' here; hash-allowing them (with 'unsafe-hashes') is not evaluated.
Hashes are computed as browsers compute them: over the UTF-8 bytes of the element’s text exactly as written between the tags, after the HTML parser’s newline normalization (CR LF and lone CR become LF), and, for attributes, over the value after character references are decoded (" becomes "). The hash field is always sha256; any of the three algorithms in the policy is matched.
External resources (§6.7.2). A nonce on <script> or <link rel=stylesheet> matching a 'nonce-…' source allows the request. A script whose integrity hashes are all listed as hash sources is allowed. With 'strict-dynamic', host, scheme and 'self' sources are ignored for scripts, so a script in the HTML needs a nonce or integrity hash; scripts that those scripts load later are trusted but cannot be seen here. Otherwise the URL is matched against each source:
*matches http and https URLs and the page’s own scheme, but notdata:,blob:orfilesystem:.'self'matches the page’s origin, and the same host over https or wss from an http page.- A scheme source such as
https:matches that scheme;http:also matcheshttps:,ws:matcheswss:,http:andhttps:. - A host source matches its host (
*.example.commatches subdomains, notexample.comitself), its scheme if given (with the same upgrades), its port (or the scheme’s default port, with 80 upgrading to 443;:*matches any port), and its path: a path ending in/matches everything under it, any other path matches exactly. 'none'matches nothing; listed with other sources it is ignored.
Relative URLs resolve against the page URL, or against a <base href> that base-uri allows.
Not evaluated. Nothing is fetched, so redirects, CSS @import and url(), workers created by scripts, fetch() calls, and anything scripts add to the page at run time are not seen. frame-ancestors, sandbox, upgrade-insecure-requests, trusted-types and reporting directives are parsed and listed but have nothing to check in a document. The tokenizer reads the document as the HTML parser does for the cases CSP depends on (comments, raw text in <script>, <style>, <textarea> and <title>, attribute quoting, the first of duplicate attributes), but it does not build a tree, so it cannot tell <source> in <picture> from <source> in <video> (srcset is treated as images, src as media) and does not handle the rare <!--<script> escape inside scripts.
Limits. API: policy at most 20,000 characters and html at most 500,000. The page has no limit.
API. POST /api/csp/check with policy, html and url. Something blocked is a 200 response; only a missing policy or an invalid url is an error. With format=text, the response is allowed, or blocked: <n> followed by one line per blocked finding.
API & MCP
The same logic is available over HTTP and as MCP tools. Unlike this page, these run on the server: see where your data goes.
