Learn · Topic explainer
What is Content Security Policy (CSP)? A practical explainer
Content Security Policy (CSP) is the browser-enforced rule set that tells the browser which sources of scripts, styles, images, and connections are allowed to run on your pages. It's the single most effective defense against cross-site scripting (XSS) and supply-chain script compromise — and also the security header most teams ship incorrectly the first time. This explainer covers what CSP actually does, the directives that matter for 90% of sites, and the staged rollout that keeps your homepage from breaking.
Why CSP exists
Before CSP, an XSS bug — say, an unescaped comment field that lets an attacker inject `<script>` — gave the attacker the same powers as your own JavaScript: read cookies, hit your APIs, exfiltrate user data. The browser had no way to distinguish your code from the attacker's. CSP changes that contract: you publish a list of trusted sources, and the browser refuses to execute anything outside that list. Even a successful injection becomes a no-op because the browser blocks the attacker's script before it runs.
The directives you'll actually use
CSP has two dozen directives, but most sites only need five. `default-src` is the fallback for every other directive. `script-src` controls which JavaScript runs. `style-src` controls stylesheets and inline styles. `img-src` controls image sources. `connect-src` controls fetch/XHR/WebSocket targets. The rest (`font-src`, `media-src`, `frame-src`, etc.) inherit from `default-src` unless you override them. A starter policy of `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; base-uri 'self'; form-action 'self'` covers most static or server-rendered sites.
Inline scripts and the nonce pattern
The biggest practical hurdle is inline `<script>` tags — common in server-rendered apps that bootstrap state into the page. The temptation is to add `'unsafe-inline'` to `script-src`, which works but disables most of CSP's value. The right fix is the nonce pattern: generate a 16-byte random nonce per request, include it in your CSP header (`script-src 'nonce-abc123'`), and add the same nonce as an attribute on every inline script (`<script nonce='abc123'>`). The browser only executes scripts whose nonce matches.
Rolling out CSP without breaking your site
Ship your first CSP as `Content-Security-Policy-Report-Only` plus a `report-uri` (or `report-to`) directive pointing at an endpoint that just logs the JSON body. Browsers obey nothing — they post what *would* have been blocked. Run report-only for at least one full release cycle so you catch route-specific third-party SDKs (analytics, chat widgets, embeds, ads). Once the report stream stabilizes, swap the header name to `Content-Security-Policy` to enforce. Keep `report-uri` even after enforcing — you want to know immediately if a deploy regresses.
Common mistakes to avoid
Three patterns burn most teams: (1) Adding `'unsafe-inline'` and `'unsafe-eval'` together — that effectively disables CSP for scripts. (2) Allowing wildcard sources like `*.example.com` for script-src — one compromised subdomain compromises everything. (3) Forgetting `frame-ancestors` — the modern replacement for `X-Frame-Options`. Add `frame-ancestors 'none'` (or `'self'`) to prevent your pages from being framed. CSP `frame-ancestors` overrides X-Frame-Options when both are present, so it's worth migrating to the CSP-native control once you have a policy in place.
Related Scorifya checks
Stack guides for hands-on rollout
Try the focused tools
Single-purpose checkers that test exactly what this topic covers.
See how your site scores
Run a free Scorifya scan on any URL you're allowed to test. The score breaks down across TLS, security headers, exposure, cookies, and DNS — exactly the areas this explainer covers.