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.
Quick answer
Content Security Policy (CSP) is an HTTP response header that tells the browser which sources of scripts, styles, and other resources are allowed to load. It is the most effective defense against cross-site scripting (XSS): even a successful injection cannot run if its source is not on the allowlist. A practical starter policy is default-src 'self', tightened per directive from there.
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.
The CSP directives most sites need
| Directive | Controls | Common starter value |
|---|---|---|
| default-src | Fallback for every other fetch directive | 'self' |
| script-src | Which JavaScript is allowed to run | 'self' |
| style-src | Stylesheets and inline styles | 'self' 'unsafe-inline' |
| img-src | Image sources | 'self' data: https: |
| connect-src | fetch, XHR, and WebSocket targets | 'self' |
| frame-ancestors | Who may embed your pages in a frame | 'none' |
Frequently asked questions
- What does Content Security Policy do?
- CSP tells the browser which sources of scripts, styles, images, and connections are trusted, and the browser refuses to load anything else. Even if an attacker injects a script through an XSS bug, the browser blocks it because its source is not on your allowlist.
- What is the difference between unsafe-inline and unsafe-eval?
- unsafe-inline allows inline <script> and <style> tags and inline event handlers; unsafe-eval allows eval() and new Function(). Each weakens CSP, and using both together effectively disables CSP's script protection. Prefer per-request nonces or hashes over unsafe-inline.
- Is unsafe-inline safe to use?
- It significantly weakens CSP, because it lets any inline script run, including one an attacker injects. The safer pattern is a per-request nonce (script-src 'nonce-...') or a hash of each trusted inline script.
- How do I add a CSP without breaking my site?
- Ship it first as Content-Security-Policy-Report-Only with a reporting endpoint. Browsers block nothing but report what would have been blocked. Run that for a full release cycle to catch third-party SDKs, then switch the header to the enforcing Content-Security-Policy.
- Does CSP replace X-Frame-Options?
- Yes. The frame-ancestors directive is the modern replacement and overrides X-Frame-Options when both are present. Use frame-ancestors 'none' or 'self' to control who may frame your pages.
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.