← All cybersecurity posts

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

  1. Inventory scripts: your bundles, analytics, payments, chat widgets, A/B testing.
  2. Remove inline event handlers like onclick="...", and javascript: URLs. CSP blocks them.
  3. Add nonces to script tags server-side. Many frameworks, including Next.js middleware, support this.
  4. Deploy in report-only mode:
Content-Security-Policy-Report-Only: script-src 'nonce-...' 'strict-dynamic'; object-src 'none'; base-uri 'none'; report-to csp
  1. Review violation reports for a week or two and fix legitimate breakages.
  2. Switch to enforcing and keep reporting on.

Other useful directives

DirectivePurpose
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-requestsUpgrades HTTP subresources to HTTPS

Common mistakes

  • Using 'unsafe-inline' in script-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-ancestors or 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.

Find what attackers can see, before they do.

AI pentesting agents test your web app, API and cloud, and every finding comes with evidence and a fix. First report in about 10 minutes.

Stay ahead of attackers.

New vulnerabilities, breach write-ups and practical fixes. A few emails a month.