← The ten steps · Step 4 of 10 · 6 minutes
The directives that matter
There are around twenty directives. Nine of them carry almost all of the value, and they are not the nine most tutorials start with.
The floor
default-src
The fallback for everything the page fetches. Set it to 'self' and you have a
sane baseline: anything you forget to name specifically inherits a restriction rather than a hole.
default-src 'self'
The one that actually stops attacks
script-src
Where scripts may come from, and whether inline script and eval are permitted. This is
the directive CSP exists for. Everything in steps 5 to 7 is about getting this one right.
Two sub-directives exist and are worth knowing because browsers report them:
script-src-elem governs <script> elements, and
script-src-attr governs inline event handlers like onclick=. If you set only
script-src, both inherit from it.
The four that never inherit
These do not fall back to default-src, because they do not describe a fetch. If you have
not written them, they are not set — and unset means unrestricted. This is the most common gap in
otherwise good policies.
base-uri
An injected <base href> rewrites where every relative URL on the page resolves,
including your own nonced <script src="app.js">. Nothing in script-src
stops it.
base-uri 'none'
form-action
A <button formaction> overrides where a form submits. Not a script, not a
navigation, not covered by anything else.
form-action 'self'
frame-ancestors
Who may put your page in an iframe. Supersedes X-Frame-Options and, unlike it, can name
several origins.
frame-ancestors 'none'
object-src
Governs <object> and <embed>. It does inherit from
default-src in current browsers, but it did not always, and there is essentially never a
reason not to say it.
object-src 'none'
The three you will tune
style-src
Where CSS may come from and whether inline styles are allowed. Lower stakes than script, but
'unsafe-inline' here still enables CSS-based data exfiltration through attribute
selectors — a real technique, just a slower one.
img-src
Where images may load from. Frequently set to * because images feel harmless. They are
not entirely: an image URL is an outbound request an attacker can put data into, so a wildcard here is a
ready-made exfiltration channel after an injection.
connect-src
Where fetch, XHR, WebSockets, EventSource and sendBeacon may
talk to. This is your API allowlist, and it is the directive that decides where data can go once script
is already running.
A complete starting policy
default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; report-uri <your endpoint>
Ship that in report-only mode, read what it catches, and widen the three tunable directives to fit
what you actually load. Do not widen script-src by adding hosts — step 5 explains why, and
step 6 gives you the alternative.
You can do this now
Paste your current policy into the analyser. It checks specifically for the four that never inherit, and it is very common to be missing three of them.