CSP tools
Content-Security-Policy in practice: what each directive governs, how nonces, hashes and source expressions match, and how to roll a policy out with Report-Only.
1 tools
What a policy actually is
Content-Security-Policy is a response header made of directives, each holding a
list of source expressions. Before the browser fetches a subresource, runs
inline code, submits a form, or lets the page be framed, it consults the governing
directive; if nothing matches, the request never happens and a violation
is reported. Enforcement happens in the browser, and a policy only removes
capabilities, never grants them.
Matching works on origins — scheme, host, port — not on the URL you had in mind:
'self' on https://app.example.com covers neither https://cdn.example.com
nor its subdomains. A policy is also worth only as much as its weakest escape
hatch: script-src 'self' 'unsafe-inline' hands anyone who can inject markup a
working script tag — the attack CSP exists to stop. Replace 'unsafe-inline'
with a per-response nonce or a content hash.
Policies compose by intersection, never union: when a response carries two
Content-Security-Policy headers — yours plus one a CDN added — a resource must
satisfy both, so an extra header only tightens the page.
Directive reference
| Directive | Governs | default-src fallback? |
|---|---|---|
script-src | script elements, eval, inline handlers | yes |
style-src | style elements, stylesheet links, @import, style attrs | yes |
connect-src | fetch, XMLHttpRequest, WebSocket, EventSource, sendBeacon | yes |
img-src / font-src / media-src / manifest-src | images and srcset; webfonts; audio, video, track; app manifest | yes |
object-src | object and embed — always 'none' | yes |
frame-src / child-src / worker-src | nested documents; workers | yes, workers via child-src |
base-uri / form-action | values a base tag may set; where forms may submit | no |
frame-ancestors | who may embed this page; supersedes X-Frame-Options | no |
sandbox / require-trusted-types-for | sandbox flags on this document; DOM XSS sinks | no |
report-uri / report-to | where violation reports are POSTed | no |
The right column bites: default-src 'self' still leaves base-tag hijacking,
form exfiltration and clickjacking open. script-src-elem and script-src-attr
split elements from inline handlers.
Source expressions
| Expression | Matches | Watch out |
|---|---|---|
'self' / 'none' | the document’s exact scheme, host and port / nothing | not subdomains; 'none' is void beside any other value |
https: | any host on that scheme | every CDN on the internet |
https://cdn.example.com | that origin, default port implied | path prefixes are not enforced across redirects |
*.example.com / * | any subdomain, any depth / any host | not bare example.com; * excludes data:, blob: |
'nonce-…' | elements carrying the matching nonce attribute | 128+ random bits, fresh per response |
'sha256-…' | inline code whose exact bytes hash to this | one byte of whitespace changes the hash |
'strict-dynamic' | scripts created by an already-trusted script | host and scheme sources become ignored |
'unsafe-inline' | any inline script or style | ignored when a nonce or hash is present |
'unsafe-eval' / 'unsafe-hashes' | eval and new Function / hashes on onclick, style | 'wasm-unsafe-eval' is the narrow version |
Rolling out with Report-Only
Ship the candidate as Content-Security-Policy-Report-Only: evaluated
identically, blocking nothing, and servable next to an enforcing policy, so you
can tighten a second while the live one protects users. Collect
violations with report-uri /csp-reports (deprecated, universally supported) or
report-to, which needs a Reporting-Endpoints header; report delivery is
exempt from connect-src. Cross-origin blocks collapse to an origin in
blocked-uri, naming the host refused, not the file; add 'report-sample' for a
code snippet and group by effective-directive.
Which tool for which job
When a site is live, the question is what policy is actually delivered. The
HTTP Headers Checker fetches the URL from
api.sitekits.dev and returns status, redirect count and every response header,
never the body — revealing a proxy that rewrote your policy. Internal
addresses are refused by its SSRF guard.
If you are drafting instead, the
Content Security Policy Generator gives 14 directive fields
over a hardened default-src 'self'; frame-ancestors 'none'; base-uri 'self'; object-src 'none' baseline, plus an upgrade-insecure-requests checkbox that
is on by default — so an untouched form already emits that baseline with
upgrade-insecure-requests appended. The string is rebuilt as you type, in the
browser. Directives outside those fields (report-to, sandbox,
require-trusted-types-for) get appended by hand.
Allowlisting an existing app is an inventory problem: export a HAR from DevTools,
read it in the HAR File Viewer, then run surprising URLs
through the URL Parser to reduce each to the scheme-host-port form
a source expression needs. Sanitize with the
HAR File Sanitizer before attaching it to a ticket — HARs carry
cookies and Authorization headers. The Text Diff Checker shows
what changed between report-only and enforcing versions; the
JSON Formatter makes a csp-report payload readable; and the
REST API Tester calls your target directly from the
browser, with no sitekits server in the path — and under its page policy,
which relaxes connect-src to 'self' https:, not under yours. A call that
succeeds there but fails inside your app points at your own header. It will not
name the cause: a connect-src block and a CORS rejection both surface as one
fetch TypeError, and the tool prints a single message covering both. Also:
HTTP hub, security hub,
security engineer toolkit.
Common breakages
Adding a nonce silently disables 'unsafe-inline'
Tag managers and chat widgets that inject their own script tags stop executing,
since they never see your per-response value; 'strict-dynamic' is the fix.
CSP hashes are base64 of the digest, not hex
'sha256-…' wants base64 of the raw 32 digest bytes, so hex from the
Hash Generator run through Base64 Encode / Decode
gives a wrong string. Copy it from the browser’s console error.
A hard-coded nonce is 'unsafe-inline' with extra steps
Nonces must be regenerated per response, which makes them server-side; a constant in a template — or HTML cached at the edge while the header regenerates — is guessable. The UUID v4 Generator suits local hand-testing, never a rollout.
A meta-tag policy cannot express half of CSP
frame-ancestors, sandbox, report-uri and Report-Only are ignored in
meta http-equiv, and the policy covers only markup after it.