How to actually test your Content Security Policy
A Content Security Policy is meant to stop XSS and a class of injection attacks. Most CSPs in production are ornamental.
I’ve audited a few. Here’s how I figure out whether yours actually works, and how to fix it if it doesn’t.
Step 1: get the actual policy
curl -sI https://yoursite.com | grep -i content-security-policy
Read it. Out loud, if you have to. CSPs are unreadable by default, one giant string of directives. If you see any of these, you have a problem:
'unsafe-inline'inscript-src. This disables CSP’s main protection.'unsafe-eval'inscript-src. Same thing, slightly more specific.*as a source. Allows anything. Why bother.https:with no host. Same as*, basically.
A real CSP has nonces or hashes, named hosts, and no wildcards. It’s verbose. That’s the point.
Step 2: test that it actually blocks things
Open your site. Open DevTools console. Try to inject a script:
const s = document.createElement('script');
s.src = 'https://example.com/evil.js';
document.head.appendChild(s);
If your console logs a CSP violation, the policy works. If the script loads, your policy is too permissive. Investigate.
Try inline injection too:
document.body.innerHTML += '<img src=x onerror="alert(1)">';
If alert fires, you have an XSS hole and your CSP isn’t catching it.
Step 3: check report-uri
A CSP without report-uri or report-to is a CSP that fails silently in production. You should be collecting violation reports. Tools like Report URI or even a simple Cloudflare Worker endpoint will work. Without this, you have no idea when an attack is being blocked, or when your own legitimate code is being blocked.
The first time I added a report endpoint to a site, I got 200 reports in the first day from extensions injecting scripts. After filtering those, I had 12 reports of actual issues, one of which was a third-party widget I’d forgotten to whitelist.
Step 4: the most common problems
In order of how often I see them:
Inline event handlers. <button onclick="..."> violates almost any reasonable CSP. The fix is to use addEventListener and tag your scripts with the nonce.
Inline styles. style-src 'self' will block <div style="...">. You either need 'unsafe-inline' for styles (acceptable, mostly) or to refactor inline styles into CSS classes (better).
Webpack/Vite chunk loading from a CDN. If your build output expects scripts to come from cdn.example.com but your CSP only lists 'self', every chunk fails to load.
Third-party widgets. Stripe, Cloudflare Turnstile, Google Tag Manager, Sentry, they all need explicit allowances. Read the docs, don’t guess.
Step 5: tighten over time
If you can’t ship a strict CSP all at once, ship a permissive one in report-only mode first:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-{NONCE}'; report-uri /csp-report
The browser enforces nothing, but reports violations. After two weeks, you’ll have a list of every violation. Fix them. Then flip to enforcement.
What “good enough” looks like
For a typical SaaS in 2026, a working CSP looks roughly like:
default-src 'self';
script-src 'self' 'nonce-{NONCE}' https://js.stripe.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https://*.cloudfront.net;
connect-src 'self' https://api.example.com;
frame-src https://js.stripe.com;
report-uri /csp-report;
Verbose. Specific. No wildcards in script-src. No 'unsafe-eval' anywhere.
The CSP you have probably looks nothing like this. Fix it.
Join the feed
Get feature releases and engineering updates delivered to your inbox.
No spam. We email when something ships.
✓
You're on the list
We'll email you when SiteCMD is ready.