The Mistakes We See Over and Over
We've scanned a lot of CSP headers. After a while, you start to see the same patterns — the same mistakes showing up on site after site. Here are the ten most common ones, and none of them are hard to fix once you know what to look for.
1. Using unsafe-inline in script-src
This is the big one. 'unsafe-inline' lets any inline script execute, which completely undermines XSS protection. We see it on roughly half the sites that have CSP at all.
The fix: Switch to nonces. Your server generates a random nonce per request, you add it to the CSP header and to your script tags. Most frameworks have built-in support for this.
2. Using unsafe-eval
'unsafe-eval' allows eval(), new Function(), and setTimeout with string arguments. These are all ways to turn a string into executable code, which is exactly what attackers want to do.
The fix: Audit your JavaScript for eval usage. It's usually in template libraries or legacy code. Modern alternatives almost always exist — you just have to find them.
3. Wildcard Sources
script-src * means "load scripts from literally anywhere." You might as well not have a CSP.
The fix: List your actual script origins. Yes, it's more work. Yes, it's worth it. If you use multiple CDNs, list each one explicitly.
4. No default-src Directive
If you set script-src but forget default-src, every other resource type (styles, images, fonts, connections) has no restrictions at all. Directives without explicit values don't inherit from script-src — they just default to allowing everything.
The fix: Always start your policy with default-src 'none' or default-src 'self', then add specific directives for what you actually need. Restrictive by default, permissive where needed.
5. Forgetting object-src
Plugins are mostly dead (bye, Flash), but object-src still matters. Without it, the default applies — and if your default is permissive, that's a potential vector.
The fix: Add object-src 'none' to your policy. Unless you're embedding Flash content in 2025, there's no reason not to.
6. Trusting Entire CDN Domains
Allowlisting cdn.jsdelivr.net or cdnjs.cloudflare.com in your script-src sounds reasonable — you're loading jQuery or whatever from there. The problem: anyone can publish packages to these CDNs. An attacker can host malicious code on the same domain you've trusted.
The fix: Use strict-dynamic with nonces, which trusts scripts based on how they're loaded rather than where they come from. Or use Subresource Integrity (SRI) hashes on your CDN script tags.
7. Deploying Straight to Enforcement
Skipping report-only mode and going directly to an enforcing CSP is like deploying to production without testing. Things will break, users will complain, and you'll end up ripping out the CSP in a panic.
The fix: Deploy with Content-Security-Policy-Report-Only first. Monitor reports for a few weeks. Fix violations. Then switch to enforcing. It takes longer but it actually works.
8. Not Monitoring Violations
Setting up CSP without a report endpoint is flying blind. You won't know when things break, and you won't know when someone is probing your policy for weaknesses.
The fix: Add report-uri or the newer report-to directive and actually look at what comes in. There are free services that aggregate CSP reports if you don't want to build your own.
9. Allowing data: URIs in script-src
The data: scheme lets you embed content inline as a URI. In script-src, this means an attacker can encode a malicious script as a data URI and bypass your CSP.
The fix: Remove data: from script-src. It's fine in img-src (data URIs for images are harmless), but keep it out of script and object sources.
10. Using Only a Meta Tag
You can deliver CSP via a <meta> tag instead of an HTTP header. It works, sort of — but it can't use report-uri, doesn't support frame-ancestors, and is vulnerable to injection attacks that modify the DOM before the meta tag is parsed.
The fix: Use the HTTP header. The meta tag is okay as a fallback or for static hosting where you can't set headers, but it shouldn't be your primary delivery method.