CSP Generator & Analyzer
Paste a Content-Security-Policy header for a graded audit with per-directive explanations and fixes, then build a strict policy with nonces, inline-script hashes and copy-ready snippets for your stack.
100% client-side. The parser, the analyzer, the nonce generator (crypto.getRandomValues) and the inline-script hasher (crypto.subtle.digest) all run in your browser — your policies and script bodies are never uploaded or logged. Check DevTools → Network: zero requests.
Analyze a policy
A bare header value, a full Content-Security-Policy: … line, or a pasted <meta http-equiv> element all parse. 0 directives detected.
Build a policy
'nonce-{NONCE}'16 random bytes, base64. Generate a fresh one per HTTP response — never reuse or cache it.
default-srcFallback for every fetch directive that is not set explicitly
script-srcWhere JavaScript may be loaded from and whether inline script may run
style-srcWhere CSS may come from and whether inline style is allowed
img-srcImage sources, including favicons and CSS background images
font-srcWeb font sources (@font-face)
connect-srcfetch(), XHR, WebSocket, EventSource, sendBeacon targets
object-src<object>, <embed> and <applet> — legacy plugin content
base-uriWhat the <base> element may set the document base URL to
form-actionWhere <form> submissions may be sent
frame-ancestorsWho may embed this page in a frame (anti-clickjacking)
upgrade-insecure-requestsRewrites http:// subresource URLs to https:// before fetching
This directive takes no values.
Inline-script SHA-256 hasher
Paste the exact body between <script> and </script> — not the tags themselves. Every byte counts: one changed space produces a different hash and the script is blocked.
…8 characters hashed. A hash source only covers <script> element content — inline event-handler attributes need 'unsafe-hashes' as well, which is better avoided.
Deploy it
Content-Security-Policy: default-src 'self'; script-src 'nonce-{NONCE}' 'strict-dynamic' https: 'unsafe-inline'; style-src 'self' 'nonce-{NONCE}'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requestsWhat this policy is likely to break
- Runtime CSS-in-JS and inline style attributes will be blockedstyled-components, Emotion, MUI, Vue scoped styles and most component libraries inject <style> elements at runtime, and many UI libraries write inline style="…" attributes for animation and positioning. Without 'unsafe-inline', a nonce passed to the styling runtime, or 'unsafe-hashes' for attributes, these are blocked and the page renders unstyled.
- Google Fonts needs two different directivesThe stylesheet comes from https://fonts.googleapis.com (style-src) and the font files come from https://fonts.gstatic.com (font-src). Allowing only one of them produces a page that loads the CSS and then silently falls back to a system font — add both, or self-host the fonts and need neither.
- blob: workers are blockedWebpack/Vite worker bundles, pdf.js, Monaco, ffmpeg.wasm and most image-processing libraries create workers from blob: URLs. If any of those are in the bundle, add blob: to worker-src (not to script-src).
- base-uri 'none' removes <base> entirelyThis is the right default, but a few legacy apps and some documentation generators rely on a <base href> to resolve relative links. If routing breaks after deploying, switch to base-uri 'self'.
- Cross-origin form posts are blockedform-action 'self' blocks hosted payment pages (classic Stripe Checkout POSTs, PayPal, 3-D Secure), SAML SSO assertions and any <form> that posts to a partner. Add those origins explicitly.
- 'strict-dynamic' does not cover document.write()Trust only propagates to scripts created with createElement/appendChild (non-parser-inserted). Anything that injects a script tag via document.write(), innerHTML or a parser-inserted path is still blocked — some older ad and A/B-testing tags do exactly that.
Diff two policies
Paste the policy you ship today and the one you are about to ship. Useful before moving a Report-Only candidate into enforcement.
3 directives changed · 5 sources added · 2 removed
script-srcchangedbase-uriaddedform-actionaddedHow the grade works
A policy starts at 100 points. Each finding deducts: critical 40, high 18, medium 9, low 4, info 0. Caps then apply and can only lower a letter, never raise it.
- • A: Score 90-100 and no critical or high findings — scripts are locked to nonces/hashes (or a tight allow-list), object-src is 'none', and base-uri, form-action and frame-ancestors are all set.
- • B: Score 80-89, or capped here by a single high finding. A solid policy with one real weakness left — typically a missing object-src/base-uri or a broad allow-list.
- • C: Score 65-79, or capped here by two or more high findings. The policy restricts something, but an attacker with an injection point has workable options.
- • D: Score 50-64. Any policy with a critical finding is capped here at best — something defeats the policy's own purpose: live 'unsafe-inline' in script-src, a data: script source, or no script control at all.
- • F: Score below 50 — a critical finding alongside other gaps, several high findings at once, or a policy that does not parse. Caps never raise a letter, so a critical finding plus a handful of mediums lands here rather than at D.
The five rules that decide whether a CSP works
- • 'unsafe-inline' is ignored when a nonce or hash is present. A nonce-source or hash-source in the same directive neutralises it, which is why strict policies keep it as a fallback for CSP1-era browsers.
- • 'strict-dynamic' discards your allow-list. Host sources, scheme sources and 'self' are ignored for scripts; trust propagates from nonced scripts to the scripts they create — but not through
document.write(). - • Four directives never fall back to default-src:
base-uri,form-action,frame-ancestorsandsandbox. Omitting them means no protection at all, however strict the rest of the policy is. - • A meta tag cannot deliver
frame-ancestors, report-uri, report-to, sandbox, has no Report-Only form, and only covers resources requested after it is parsed. - • Two CSP headers intersect, they do not merge. A resource must satisfy every policy sent, so adding a second header can only ever tighten. A duplicated directive inside one policy is different: the first occurrence wins and the second is ignored.
Related tools
About CSP Generator & Analyzer
The CSP Generator & Analyzer does the two jobs a Content-Security-Policy actually requires: understanding the header you already ship, and writing a better one. The analyzer parses a header value, a full header line or a pasted <meta http-equiv> element, resolves every directive through the CSP fallback chain, and reports what the policy really allows — including the directives that never fall back to default-src. Findings are ranked critical to info, each with a paragraph of reasoning and a concrete fix, and the policy gets an A-F grade whose scoring rule is stated in full. The generator builds a policy from strict presets, mints a nonce with crypto.getRandomValues, hashes inline scripts with the Web Crypto API, and emits copy-ready snippets for seven deployment targets.
Why use a CSP Generator & Analyzer?
Most CSP tools either generate a policy or grade one, and the generated policies are usually allow-lists with 'unsafe-inline' still in them — which is the shape research has repeatedly shown to be trivially bypassable. This tool treats the two halves as one workflow: analyze what you ship, see exactly which finding is costing you the grade, rebuild the policy, and read the breakage warnings before you deploy rather than after Google Tag Manager stops firing. It also encodes the details that trip people up in production: 'unsafe-inline' is ignored when a nonce or hash is present, host allow-lists are ignored under 'strict-dynamic', base-uri and form-action and frame-ancestors have no default-src fallback, and a <meta> tag cannot deliver frame-ancestors, sandbox or report-uri at all.
Who is it for?
Developers rolling out their first CSP and trying to work out why the site went blank, security engineers reviewing a policy in a pentest report or a compliance questionnaire, platform teams migrating from an allow-list to a nonce-based policy, and anyone who has been handed a 'add a Content-Security-Policy header' ticket and needs the resulting header to be both strict and deployable.
How to use the tool
Paste your policy into the analyzer at the top — a bare header value, a full 'Content-Security-Policy: …' line copied from DevTools or curl -I, or a whole <meta http-equiv> element all parse.
Read the grade and the finding list: critical and high findings are the ones that make the policy ineffective, medium and low are hardening, and info items explain behaviour rather than problems.
Open 'What this policy allows' to see each directive in plain English, including which ones are only inherited from default-src and which have no fallback at all.
Switch to the generator and pick a preset — strict nonce-based, strict hash-based, a Report-Only starter, or an allow-list for a typical site with GA4, Google Fonts and a CDN.
Add or remove sources per directive, toggle keywords such as 'self', 'strict-dynamic' and 'unsafe-inline', and press 'New nonce' to mint a fresh 128-bit nonce for the policy.
Paste any inline <script> body into the hasher to get its 'sha256-…' source, which is how you allow-list inline code on a statically cached page where a per-response nonce is impossible.
Check the breakage warnings before deploying — they name the things this policy will block, from tag managers and GA4 beacons to CSS-in-JS, data: images and blob: workers.
Flip the Report-Only switch, copy the snippet for your stack (nginx, Apache, Next.js, Cloudflare Pages _headers, Express helmet or a meta tag), ship it, watch the reports, then re-copy the same value to the enforcing header.
Frequently Asked Questions
How do I create a Content Security Policy for my site?
Start from a preset rather than a blank page. Pick the strict nonce-based preset if your HTML is rendered per request, or the hash-based one if it is statically cached, then adjust the source lists for the third parties you actually load. Ship it as Content-Security-Policy-Report-Only first with a reporting endpoint attached: nothing is blocked, but every violation is reported, so you discover the widget nobody remembered before your users do. Leave it in Report-Only for a full business cycle — including whatever marketing deploys monthly — fix or allow-list what appears, then copy the identical value into the enforcing header.
What is a Content Security Policy and what does it actually stop?
CSP is a response header that tells the browser which sources of script, style, images, frames and connections it may use on your page, and it is enforced by the browser itself rather than by your code. Its main job is to reduce the impact of cross-site scripting: even if an attacker injects markup, a strict policy stops the injected script from executing. Secondary directives cover related attacks — frame-ancestors blocks clickjacking, base-uri blocks base-tag hijacking, form-action blocks credential-harvesting form rewrites, object-src blocks legacy plugin execution. CSP is a second line of defence; it never replaces output encoding and input validation.
Is it safe to paste my production CSP into this tool?
Yes. Everything here runs entirely in your browser via JavaScript — the parser, the analyzer, the nonce generator and the SHA-256 hasher all execute locally, and nothing you type is uploaded, logged or stored. You can verify it in DevTools: open the Network tab, paste a policy, generate a nonce and hash a script, and watch zero requests appear. That also matters for the inline-script hasher, since the script body you paste is often proprietary application code. A CSP header is public anyway — any visitor can read it — but the tool is built so your unreleased policies and script bodies never leave the machine.
Should I use 'strict-dynamic' with a nonce, or a host allow-list?
Prefer the nonce plus 'strict-dynamic'. Research presented at CCS 2016 found that the large majority of real allow-list policies were bypassable, because popular CDNs host JSONP endpoints, old framework builds or user-uploadable paths that let an attacker execute code from an allow-listed origin without violating the policy. Allow-lists also rot: entries accumulate and nobody dares remove them. With 'strict-dynamic', only scripts carrying your per-response nonce are trusted, plus whatever those scripts load themselves — so the policy stops depending on the trustworthiness of every host you ever allowed. Keep the allow-list after 'strict-dynamic' purely as a fallback for older browsers.
Why does 'unsafe-inline' defeat a CSP, and when is it ignored?
'unsafe-inline' in script-src permits inline script blocks and inline event-handler attributes, which is exactly the capability an XSS payload needs — the attacker never has to host a file anywhere, so the rest of the policy becomes decoration. The important exception is that browsers ignore 'unsafe-inline' entirely whenever the same directive also contains a nonce or a hash, and 'strict-dynamic' additionally makes them ignore host sources, scheme sources and 'self'. That is why strict policies deliberately keep 'unsafe-inline' at the end: CSP1-era browsers that do not understand nonces fall back to it and keep working, while every modern browser enforces the strict rules.
Nonce or hash — which one should I use for inline scripts?
Use a nonce when the server renders HTML per request: generate at least 16 random bytes, base64-encode them, put the same value in the header and on every inline script tag, and never cache the response. Use hashes when the HTML is static or cached at a CDN, because a nonce baked into a cached page is identical for every visitor and therefore useless. A hash covers the exact bytes of one script body: change a single space and the hash changes, so hashes suit build-time generation. Hashes do not cover inline event-handler attributes unless you add 'unsafe-hashes', which is best avoided.
Do I still need X-Frame-Options if I set frame-ancestors?
frame-ancestors supersedes X-Frame-Options in every browser that supports CSP, and where both are present the CSP directive wins, so there is no conflict to resolve. frame-ancestors is also strictly more expressive: X-Frame-Options only understands DENY and SAMEORIGIN, because its ALLOW-FROM variant was never implemented consistently and is dead, whereas frame-ancestors takes an origin allow-list. Keeping X-Frame-Options: DENY alongside costs one header and helps ancient clients, provided the two agree. Two traps: frame-ancestors never falls back to default-src, so omitting it means no CSP framing protection at all, and a meta-tag CSP cannot deliver it.
What breaks when I deploy a CSP, and what can a meta tag not do?
The usual casualties are Google Tag Manager and GA4 (the bootstrap is inline, injected tags need 'strict-dynamic', and beacons need the collector in connect-src), Google Fonts (stylesheet in style-src, font files in font-src), CSS-in-JS runtimes that inject style elements, data: images, blob: workers, and third-party iframes such as Stripe or YouTube. A <meta http-equiv> policy has extra limits: it cannot deliver frame-ancestors, report-uri, report-to or sandbox, it has no Report-Only form, and it only applies to resources requested after the browser parses it. Deliver CSP as a real response header whenever you can.
Share This Tool
Found this tool helpful? Share it with others who might benefit from it!
💡 Help others discover useful tools! Sharing helps us keep these tools free and accessible to everyone.