katlab tools/csp support on Ko-fi

CSP Builder · Content-Security-Policy analyzer

Paste an existing policy or start from a preset, edit each directive's sources, and see the effective policy and what's risky about it — then copy it out for your server.

Everything runs locally in your browser. Nothing is sent to a server.

Presets

Directives

Hash an inline script or style ('sha256-…' source)

Analysis

Effective policy per request type

Request typeGoverned byAllowed sources

Output


      

What a Content-Security-Policy actually does

A Content-Security-Policy is a response header that tells the browser which sources a page may load scripts, styles, images, frames, fonts and connections from, and whether inline code and eval() are allowed. Its main job is damage control for cross-site scripting: if an attacker manages to inject markup into your page, a good policy stops the injected <script> from running or from sending data anywhere. It is defence in depth, not a replacement for escaping output.

A policy is a list of directives separated by semicolons. Each directive has a name, like script-src, followed by source expressions: keywords such as 'self' and 'none' (always single-quoted), schemes such as https: or data:, hosts such as cdn.example.com or *.example.com, and 'nonce-…' or 'sha256-…' values. The editor above validates each source against the CSP Level 3 grammar and flags common mistakes, like writing self without quotes, which browsers read as a host named "self".

How fallback works — and where it doesn't

Fetch directives inherit when they are missing. script-src-elem and script-src-attr fall back to script-src, then default-src; the style directives work the same way. worker-src checks child-src, then script-src, then default-src. frame-src falls back to child-src and then default-src. The "effective policy" table resolves these chains so you can see which directive actually governs each request type.

Several directives never fall back to default-src: base-uri, form-action, frame-ancestors, sandbox, the reporting directives and upgrade-insecure-requests. A policy of just default-src 'self' therefore still lets an injected <base> tag repoint relative URLs and lets forms post anywhere. Delivery matters too: in a <meta> tag, browsers ignore frame-ancestors, report-uri, report-to and sandbox, and Report-Only is not available — which is why the meta output strips them.

Nonces, hashes and 'strict-dynamic'

Host allowlists are hard to get right: any allowlisted domain that serves JSONP endpoints or an old AngularJS build can be used to run attacker-chosen code, and a 2016 Google study found the large majority of real-world allowlist policies bypassable. The more robust pattern is to mark trusted scripts individually. A nonce is a random value generated fresh for every response and placed both in the header ('nonce-R4nd0m…') and on each <script nonce="…">. A hash ('sha256-…') pins the exact contents of an inline script — the helper above computes one for you.

Adding 'strict-dynamic' lets scripts that were trusted via nonce or hash load further scripts, and tells supporting browsers to ignore host sources, scheme sources, 'self' and 'unsafe-inline' in that list. Likewise, once a nonce or hash is present, CSP Level 2+ browsers ignore 'unsafe-inline'. That is why policies like script-src 'nonce-…' 'strict-dynamic' 'unsafe-inline' https: are sensible: modern browsers get the strict behaviour and older ones fall back to the looser entries. The analyzer reports these as neutralised rather than as risks.

A real example: this site's own policy

tools.katlab.dev ships a CSP on every page, and it is loaded as the "katlab tools" preset so you can inspect it. There is no 'unsafe-eval' anywhere; the WebAssembly-based tools (jq, SQLite, video and PDF tools) only get 'wasm-unsafe-eval', which allows compiling WASM but not eval() or new Function(). Files you drop into a tool become blob: URLs, so blob: is allowed for images, media and connect-src — but not for scripts, so there are no blob-URL workers. frame-ancestors 'none', base-uri 'self' and form-action 'self' cover the directives that don't inherit.

It isn't perfect, and the analyzer says so: each tool is a single static HTML file with its inline script, so script-src contains 'unsafe-inline' instead of per-response nonces, and object-src relies on default-src 'self' instead of 'none'. That trade-off is acceptable for pages with no user-generated content, and a good illustration of why findings are ranked instead of pass/fail.

Should I deliver CSP as an HTTP header or a meta tag?

Prefer the HTTP header. A meta tag only applies to content after it in the document, cannot use Report-Only mode, and browsers ignore frame-ancestors, report-uri, report-to and sandbox when they appear in a meta policy. The meta tab in this tool strips those directives and tells you which ones it removed.

Why does 'unsafe-inline' seem to do nothing in my script-src?

When a source list also contains a nonce or a hash, browsers that support CSP Level 2 or later ignore 'unsafe-inline' in that list. Sites often keep it on purpose as a fallback for very old browsers. A side effect is that inline event handlers such as onclick attributes are blocked, because they cannot carry a nonce.

What is the difference between 'unsafe-eval' and 'wasm-unsafe-eval'?

'unsafe-eval' allows eval(), new Function() and string arguments to setTimeout, which turns any string injection into code execution. 'wasm-unsafe-eval' only allows compiling and instantiating WebAssembly, so it is the narrower choice when a page just needs to run WASM.

How can I test a policy without breaking my site?

Send it as Content-Security-Policy-Report-Only first. Browsers then report violations to the console and to any report-uri or report-to endpoint without blocking anything. Tick the Report-Only toggle and every output tab switches to that header name.

Is the policy I paste sent anywhere?

No. Parsing, analysis and output generation all run in JavaScript inside your browser tab. The current policy is kept in the page's URL fragment so you can bookmark or share it, and fragments are not sent to the server.