default-src is the fallback for every fetch directive you don't set; script-src is the one that decides whether injected code runs. A policy without either doesn't restrict scripts at all. Write script-src explicitly, name exact origins (never * or a bare https:), and add the four directives that default-src does not cover: object-src, base-uri, form-action, frame-ancestors.
How does the CSP fallback chain work?
When the browser needs to load a resource, it looks for the most specific directive and walks up a defined chain until it finds one that's set. Only the first match applies — the chain is not cumulative (CSP Level 3, directive fallback list):
script-src-elem → script-src → default-src (external <script> and inline blocks)
script-src-attr → script-src → default-src (onclick="…" handlers)
style-src-elem → style-src → default-src
worker-src → child-src → script-src → default-src
frame-src → child-src → default-src
img-src / font-src / connect-src / media-src / object-src / manifest-src → default-src
NO fallback (must be written explicitly or they simply don't exist):
base-uri · form-action · frame-ancestors · sandbox · report-toTwo practical consequences. First, default-src 'self' alone is a real policy — scripts, images and connections are all limited to your origin. Second, the moment you write script-src, default-src stops applying to scripts entirely: if default-src had a nonce and script-src doesn't, the nonce is gone for scripts. The CSP Checker resolves this chain the way the browser does and tells you which directive actually governed scripts when it reports a weakness.
What belongs in script-src?
'self'— scripts from your own origin (same scheme, host and port).- Exact third-party origins —
https://js.stripe.com,https://www.googletagmanager.com. Scheme + host; a path is allowed but rarely worth the brittleness. 'nonce-…'or'sha256-…'— for inline scripts; see replacing 'unsafe-inline'.'strict-dynamic'— with a nonce/hash, so trusted scripts may load further scripts without you listing every CDN. Without a nonce or hash it does nothing useful; the checker flags that combination.
What does not belong there, and why the checker grades it HIGH:
*,https:,http:— “any host”. An attacker who can inject a<script src="https://evil.example/x.js">is fully allowed.data:andblob:— let injected markup smuggle a script as a data URL. Fine forimg-src; not for scripts.- Broad CDN hosts that serve anything anyone uploads (public JS CDNs, storage buckets, some cloud-function domains). If the host also serves attacker-controllable files, listing it is equivalent to
*— Google's CSP research called these “JSONP and Angular endpoints on whitelisted hosts” the most common bypass (Weichselbaum et al.).
Which four directives does default-src not cover?
These are the “missing directive” notes the checker adds even to an otherwise good policy. They are informational because they are defence-in-depth, but each is one line and there is no reason to skip them:
object-src 'none'— blocks<object>/<embed>plugin content, a historical script-execution path. No modern site needs plugins.base-uri 'self'— stops an injected<base href>from redirecting every relative script URL on the page to an attacker's host.form-action 'self'— stops an injected form from posting user input (passwords, card numbers) to another origin. Add your payment provider's origin if you post directly to it.frame-ancestors 'none'— who may put your page in an iframe (clickjacking). Separate guide: frame-ancestors.
default-src 'none' is a fine starting point for an API or a static page, but on a normal site it blocks everything you didn't list — including same-origin images, fonts and the manifest. Start from default-src 'self' and tighten individual directives instead of starting from nothing and discovering the gaps in production.For the site owner and for the developer
For the site owner (plain English)
A Content-Security-Policy is a list of places your website is allowed to load code from. If a report says the list “allows any host” or “doesn't restrict scripts”, the list exists but has a wildcard in it — like a guest list that says “everyone”. The fix is for whoever runs the site to replace the wildcard with the actual handful of services you use (your own domain, your analytics, your payment provider). The checker can draft that list for you from what the homepage already loads.
Paste a policy or enter your domain — the checker resolves the fallback chain, names the directive that governs scripts, and hands back your policy with the four hardening directives appended.
Check and harden your policy →For the developer (exact syntax)
A starter policy for a typical marketing site with analytics and one payment provider:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic' https://www.googletagmanager.com https://js.stripe.com;
style-src 'self' 'unsafe-inline'; # styles: low risk, fix later with nonces
img-src 'self' data: https://www.google-analytics.com;
font-src 'self';
connect-src 'self' https://www.google-analytics.com https://api.stripe.com;
frame-src https://js.stripe.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
report-to csp-endpointGenerate a policy from what a page actually loads (a quick first draft, not a substitute for the report-only pass):
# List the external origins a page references in its HTML
curl -s https://example.com/ \
| grep -oE '(src|href)="https?://[^/"]+' \
| sed -E 's/.*="//' | sort | uniq -c | sort -rn# nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://js.stripe.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
# Apache
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://js.stripe.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'"Deploy it as Report-Only first; the violation reports are the definitive list of origins you forgot.