Website problems
CSP allows unsafe-inline
A Content-Security-Policy restricting which scripts may run is the main defence-in-depth against script injection. A policy whose script directive permits inline scripts permits exactly the shape an injection takes, which reduces the header to a statement of intent. The directive is almost always added because a strict policy blocked something legitimate, and it is almost always removable with a nonce or a hash once the legitimate inline scripts are enumerated.
Why the directive nullifies the policy
An injected script arrives as markup inside the page, which is inline by definition.
A policy permitting inline execution therefore permits it, regardless of how carefully the rest of the policy restricts external origins.
The same applies to the directive permitting arbitrary string evaluation, which covers a different shape of the same problem.
A policy carrying either is not worthless — it still restricts external script origins, which blocks one class of injection — but it does not stop the common one.
Which is why reading the directives matters more than checking the header exists.
How the directive gets added
A strict policy is deployed, something breaks, and the browser console names the inline script it blocked.
Adding the directive makes everything work immediately, which is a strong incentive under time pressure.
Analytics snippets, tag managers and consent platforms all ship inline bootstrap code, so the breakage is usually immediate and widespread.
Frameworks that inject inline style or state also trip it, which broadens the problem beyond scripts.
So the directive is rarely a decision about security — it is the shortest path out of a deployment that is failing.
Removing it with a nonce
The server generates a random value per response and puts it both in the policy and on every inline script it intends to allow.
An injected script has no nonce, because the attacker cannot know a value generated after their input was stored.
The value must be unpredictable and must change on every response, which means it cannot be cached alongside the page.
Where a page is cached, a hash of each inline script's content is the alternative — fixed, cacheable, and requiring the scripts never to change unnoticed.
The strict-dynamic keyword lets a nonced script load further scripts, which is what makes tag managers workable under a strict policy.
Deploying it without breaking the site
Start in report-only mode, which sends violations to an endpoint and blocks nothing.
Collect reports for long enough to see the pages nobody visits often, which is where the surprises are.
Work through the inline scripts, giving each a nonce or moving it to an external file.
Switch to enforcing mode only when the report volume has settled, and keep the reporting endpoint afterwards.
Expect third-party tags to be the hardest part, and expect to discover tags nobody remembered adding.
How to check it yourself
`curl -I https://example.com | grep -i content-security-policy` prints the policy.
Read the script directive: the inline-permitting and evaluation-permitting keywords are the ones that matter.
Check whether a nonce is present, and whether it changes between two requests — a static nonce is the same as no nonce.
Check the report-only header too, since a site may be running both and enforcing the weaker one.
Open the browser console on a few pages to see what a stricter policy would block before you deploy it.
How VeriFixScan detects it
`security.csp` reads the policy really returned and reports its directives rather than only its presence.
`cookies.httponly` is reported alongside, because it is the control that limits what an injected script can take even when the policy does not stop it.
`structure.html_quality` reports inline event handlers in the markup, which are a signal that a strict policy is not in force.
`performance.third_party_resources` lists the external origins the page contacts, which is the list a policy has to accommodate.
A policy that exists and permits inline execution is reported differently from an absent one, because the two mean different things.
What a policy does not replace
Correct output encoding, which is what prevents the injection rather than limiting its consequence.
HttpOnly on session cookies, which stops an injected script reading the credential worth taking.
Input validation, which is valuable for correctness and is the wrong tool for this specific problem.
A policy is the layer that limits damage when the others fail, and treating it as the primary defence is how a site ends up with a weak policy and unencoded output.
Frequently asked questions
- Is a policy with unsafe-inline better than no policy?
- Marginally — it still restricts external script origins. It does not stop an injected inline script, which is the common shape, so it should not be counted as that defence.
- Why does my strict policy break my tag manager?
- Tag managers bootstrap with inline code and then load further scripts. A nonce on the bootstrap plus the strict-dynamic keyword is the usual way to make both work.
- Nonce or hash?
- A nonce where responses are generated per request. A hash where the page is cached, since a nonce must change every response and a cached page cannot do that.
Sources
Related
VeriFixScan crawls a site and applies its checks to every page it reaches, keeping the evidence behind each finding. Scanning one website is free.
Scan a website