CSP, short for Content Security Policy, is a browser security mechanism in which a web application sends a declarative allowlist of permitted content sources in an HTTP response header, and the browser refuses to load or execute anything outside that list. In application security it is the main defense-in-depth layer against cross-site scripting (XSS) and related injection attacks.
CSP does not fix vulnerable code. It limits what an attacker can do after an injection succeeds, so a stray script tag inserted into a page is blocked instead of running with the user’s session. It is defined by the W3C Content Security Policy specification, currently at Level 3, and is supported by all major browsers.
How CSP enforcement works in the browser
The server delivers the policy in the Content-Security-Policy response header, or less flexibly in a meta element. The policy is a list of directives, each naming a resource type and the sources allowed for it, for example script-src 'self'. Before the browser fetches a script, image, frame or connection, it checks the request against the matching directive.
When a resource is not allowed, the browser blocks it and records a violation. By default CSP also disables inline scripts and the eval function, because these are exactly what most XSS payloads rely on. A policy can send violation reports to a collection endpoint, which lets teams see what would break before they enforce anything.
A second header, Content-Security-Policy-Report-Only, applies the same rules without blocking. Running a policy in report-only mode for days or weeks is the standard way to discover legitimate resources that a new policy would otherwise break.
Key CSP directives and source expressions
A CSP policy is built from fetch directives, document directives and navigation directives. The table lists those that matter most in practice and what each one controls.
| Directive | What it controls | Typical safe value |
|---|---|---|
| default-src | Fallback for fetch directives not set explicitly | ‘self’ or ‘none’ |
| script-src | JavaScript sources and inline script execution | ‘nonce-…’ ‘strict-dynamic’ |
| style-src | Stylesheets and inline styles | ‘self’ with nonces where possible |
| object-src | Plugins such as embed and object elements | ‘none’ |
| base-uri | Allowed values of the base element | ‘none’ or ‘self’ |
| frame-ancestors | Which sites may embed the page in a frame | ‘none’ or ‘self’ |
| connect-src | Fetch, XHR and WebSocket destinations | ‘self’ plus required APIs |
| form-action | Where forms may submit data | ‘self’ |
Source expressions inside directives include host names, schemes such as https:, and keywords. The keywords ‘unsafe-inline’ and ‘unsafe-eval’ re-enable the dangerous behaviors CSP was meant to stop, so their presence in script-src usually cancels most of the protection.
Allowlist policies versus strict policies
There are two broad styles of CSP. An allowlist policy names trusted domains, while a strict policy trusts individual scripts through nonces or hashes. Research on deployed policies showed that host allowlists are frequently bypassable, because a trusted domain often hosts JSONP endpoints or old library versions an attacker can abuse.
| Policy style | How scripts are trusted | Strength | Main drawback |
|---|---|---|---|
| Host allowlist | By domain name | Easy to read | Often bypassable through trusted hosts |
| Nonce-based strict CSP | Random per-response value on each script tag | Strong against injected markup | Requires server-side templating changes |
| Hash-based strict CSP | SHA-256 to SHA-512 digest of script content | Works for static pages | Every script change needs a new hash |
| strict-dynamic | Trust propagates to scripts loaded by trusted scripts | Compatible with loaders | Needs nonces or hashes as an anchor |
A nonce must be unpredictable and regenerated for every response, with at least 128 bits of randomness. A static or cached nonce makes the policy decorative, since an attacker can simply copy it into injected markup.
CSP within the wider application security picture
CSP addresses a specific slice of the risks described in the OWASP Top 10. It is strongest against XSS, helps with clickjacking and data exfiltration, and does almost nothing for server-side flaws. The comparison shows where it fits next to related controls.
| Threat | Effect of CSP | Primary control |
|---|---|---|
| XSS | Strong mitigation when policy is strict | Output encoding and safe templating |
| Clickjacking | Prevented by frame-ancestors | CSP or X-Frame-Options |
| CSRF | No meaningful effect | Anti-CSRF tokens and SameSite cookies |
| SQL injection | No effect | Parameterized queries |
| Mixed content | Fixed by upgrade-insecure-requests | HTTPS everywhere |
| Data exfiltration | Partial limits via connect-src and img-src | Input validation and monitoring |
The key lesson is that CSP is a second line of defense. Teams that treat it as a substitute for correct output encoding usually end up with permissive policies and real XSS bugs.
Deploying a CSP without breaking the site
Successful CSP rollouts follow an iterative, report-driven process rather than a single switch. The ordered steps below reflect common practice on large web applications.
- Inventory scripts, styles, frames and third-party services the application really uses.
- Refactor inline event handlers and javascript: URLs into external or nonced scripts.
- Deploy the policy in report-only mode and collect violation reports.
- Filter noise from browser extensions and fix legitimate violations in the code.
- Switch to enforcement, keep reporting enabled and review new violations after each release.
Third-party tags from analytics and advertising are the usual obstacle, since they inject further scripts dynamically. strict-dynamic helps here, because a trusted loader can add its own dependencies without listing every domain.
Common CSP mistakes
Many deployed policies give a false sense of security. The following errors appear repeatedly in security assessments.
- ‘unsafe-inline’ in script-src, which allows exactly the inline payloads XSS uses.
- Wildcards such as https: or *.cdn.example, which trust far more code than intended.
- Missing object-src and base-uri, which leave plugin and base tag injection open.
- Reused or predictable nonces, often caused by full-page caching.
- Report-only left in place permanently, so nothing is ever blocked.
- Conflicting meta and header policies, which combine and break pages unexpectedly.
In everyday language
Think of a web page as a party and CSP as the guest list handed to the doorman, which here is the browser. The site tells the browser in advance which sources of code are invited. If someone sneaks a malicious script into the page, it is not on the list, and the browser simply refuses to let it run.
The guest list does not stop people from trying to get in, and a careless list with too many names helps nobody. Used well, it turns many attacks into harmless blocked attempts.
Seen in the real world
An online bookstore discovers that its product review field once allowed stored XSS. While developers fix the encoding bug, the security team rolls out a nonce-based strict CSP with strict-dynamic, object-src ‘none’ and base-uri ‘none’. It runs in report-only mode for three weeks, during which reports reveal two forgotten inline handlers and an old chat widget.
After those are fixed, the policy is enforced. A month later, a penetration tester finds another injection point in a newsletter form, but the injected script is blocked by the browser and reported, so the finding is rated lower and patched with no customer data exposed.