Content Security Policy (CSP): How to Deploy a Strict Policy
A Content Security Policy tells the browser which scripts and resources are allowed to run on your page. A strict CSP turns many XSS vulnerabilities from account takeover into a harmless broken script. A weak one gives a false sense of safety.
Why allowlist policies often fail
Early CSPs listed trusted domains:
script-src 'self' https://cdn.example.com https://www.google-analytics.com
Research has shown these are frequently bypassable, because allowed domains often host JSONP endpoints, old library versions or user content that attackers can abuse. Adding 'unsafe-inline' removes most XSS protection entirely.
The strict CSP approach: nonces
A nonce-based policy allows only scripts carrying a random value generated for each response:
Content-Security-Policy:
script-src 'nonce-R4nd0m123' 'strict-dynamic';
object-src 'none';
base-uri 'none';
<script nonce="R4nd0m123" src="/app.js"></script>
An attacker injecting <script> cannot know the nonce, so their script is blocked. 'strict-dynamic' lets your trusted scripts load further scripts, which makes the policy practical with modern bundles and tag managers.
Hash-based policies for static sites
If your pages are static and cannot generate a nonce per request, you can allow specific inline scripts by their hash, such as 'sha256-...'.
Step-by-step rollout
- Inventory scripts: your bundles, analytics, payments, chat widgets, A/B testing.
- Remove inline event handlers like
onclick="...", andjavascript:URLs. CSP blocks them. - Add nonces to script tags server-side. Many frameworks, including Next.js middleware, support this.
- Deploy in report-only mode:
Content-Security-Policy-Report-Only: script-src 'nonce-...' 'strict-dynamic'; object-src 'none'; base-uri 'none'; report-to csp
- Review violation reports for a week or two and fix legitimate breakages.
- Switch to enforcing and keep reporting on.
Other useful directives
| Directive | Purpose |
|---|---|
object-src 'none' | Blocks plugins like Flash and legacy embeds |
base-uri 'none' | Prevents base tag injection that hijacks relative URLs |
frame-ancestors 'none' | Prevents clickjacking |
form-action 'self' | Limits where forms can submit |
upgrade-insecure-requests | Upgrades HTTP subresources to HTTPS |
Common mistakes
- Using
'unsafe-inline'inscript-src, which defeats the purpose. - Allowing broad domains such as entire CDNs.
- Reusing the same nonce across responses.
- Setting CSP only on the homepage, not on every route.
- Forgetting that CSP in a meta tag cannot set
frame-ancestorsor reporting.
CSP is a second layer
CSP limits the damage from XSS; it does not fix the XSS. Keep sanitising and encoding output. SecurityInfinity checks CSP on every page it discovers and flags weak directives alongside any XSS it finds. Start free.
Frequently asked questions
Does CSP slow down my site?
No. The browser enforces the policy with negligible overhead.
Can I use CSP with Google Tag Manager?
Yes, with nonces and 'strict-dynamic', though tags that inject inline scripts may need adjustment.
What is the difference between CSP and SRI?
Subresource Integrity checks that a specific external file has not been modified. CSP controls which scripts may run at all. They complement each other.