Content-Security-Policy header: minimal working setup
Ships a minimal working Content-Security-Policy header: a restrictive starting policy, a report-only trial run to catch breakage, and the common allowlist additions for real-world apps. Use when adding CSP to a site for the first time, when a scan flags a missing CSP, or when an existing CSP is a wildcard mess. Not for the full CSP directive reference, not for nonce-based CSP architectures.
TL;DR
Start with default-src 'self', run it in report-only mode for a week, then enforce. Add only the sources your app actually needs: your CDN, your analytics, your fonts. A minimal CSP you enforce beats a perfect CSP you never ship.
Content-Security-Policy header: minimal working setupUse this when
- Adding a Content-Security-Policy to a site for the first time
- A security scan flags a missing or weak CSP
- Your current CSP is default-src * and you want a real one
- You broke the site with an over-strict CSP and need to start over
Not for
- Nonce or hash-based CSP for apps with heavy inline scripts (advanced setup)
- The complete CSP directive reference (this is the minimal working version)
- CSP for browser extensions or Electron apps
Steps
1. Start with the minimal policy.
default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'. This blocks everything except resources from your own origin, kills plugins and object tags, and prevents framing.
Expected: a four-directive policy you can explain in one breath.
2. Deploy it in report-only mode first.
Send it as Content-Security-Policy-Report-Only with a report-uri or report-to endpoint. Watch the violation reports for a few days to a week to learn what your app actually loads.
Expected: a list of real resource sources your app needs, from the reports.
3. Allowlist only what the reports show you need.
Common additions: script-src for your CDN and analytics domains, style-src for your font or CSS CDN, img-src for image hosts, connect-src for your API domains. Add each source you saw in the reports, nothing speculative.
Expected: a policy that covers every legitimate source and nothing else.
4. Switch from report-only to enforcing.
Change the header name to Content-Security-Policy and keep the report endpoint. Monitor reports for the first days after enforcing.
Expected: violations blocked, reports still flowing for visibility.
5. Test the whole app.
Click through every major flow: login, checkout, uploads, embedded content, third-party widgets. CSP breaks the things you forgot to test.
Expected: full app functional with the policy enforcing.
6. Keep the reports monitored.
Violation reports are now an early warning system for injected content and for new third parties someone added without telling you. Review them regularly.
Expected: a quiet report stream, with alerts on sudden spikes.
Variant phrasings
Simplest CSP header that works
The four-directive starter in step 1, report-only first, then enforce with the sources you actually need.
CSP broke my site, how to fix
Drop back to report-only, read the violation reports to find what got blocked, allowlist those sources, re-enforce.
Content security policy for a site with Google Analytics and fonts
Add the analytics and font domains to script-src and style-src (and font-src). The reports in step 2 will show you the exact domains.
Why it happens
CSP exists because XSS is still everywhere and input sanitization alone keeps failing. The policy flips the model: instead of trying to block every bad script, the browser only runs scripts from sources you named. The minimal setup works because most apps load resources from a handful of origins, so default-deny plus a short allowlist covers reality.
Edge cases
- Inline scripts everywhere: legacy apps with inline event handlers will drown in violations. Either refactor toward external scripts or add the specific hashes, but dont solve it with 'unsafe-inline', which guts the protection.
- Third-party widgets that load more third parties: some widgets pull in chains of additional domains. The report-only phase reveals the full chain, allowlist the whole chain or reconsider the widget.
- Single-page apps with dynamic imports: make sure script-src covers the origins of lazily loaded chunks, which sometimes live on a different CDN path.
- Report endpoint gets spammed: browser extensions and misbehaving clients generate noise. Filter reports by your own user agent patterns before alerting on them.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_TBMjJusfO-xg9FnfrHc3Kg
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.