Quick Summary
- CSP is an HTTP header that declares which content sources the browser may load.
- It prevents XSS by blocking inline scripts and restricting allowed domains.
- Key directives include default-src, script-src, style-src, and connect-src.
- Always start with Report-Only mode to avoid breaking your site.
Introduction
Cross-Site Scripting (XSS) remains one of the most common web vulnerabilities. Despite decades of awareness, it continues to appear in security audits and enables some of the most damaging web attacks. Content Security Policy (CSP) is the browser's most powerful built-in defense against XSS.
A CSP is an HTTP response header that tells the browser exactly which sources of content are allowed to load on your page. Scripts from unapproved sources are blocked automatically, even if an attacker manages to inject malicious code into your HTML. This makes CSP a critical defense-in-depth layer that protects users even when application-level security fails.
This guide explains how CSP works, which directives you need, how to implement it without breaking your site, and the common mistakes that render CSP ineffective. For detailed browser specifics, consult the MDN Web Docs on CSP.
What Is a Content Security Policy?
The Power of the Whitelist
A Content Security Policy (CSP) is an HTTP response header that lets you declare exactly which sources of content the browser is allowed to load. Any content from undeclared sources is automatically blocked by the browser.
This severely limits the damage an attacker can do even if they manage to inject malicious code into your page. The browser simply refuses to execute it if the source is not on the whitelist.
Basic CSP Header
Content-Security-Policy: default-src 'self'; script-src 'self' https://analytics.example.com; img-src 'self' data: https://cdn.example.comOnly loads resources from your domain, analytics from one approved source, and images from your CDN. Everything else is blocked.
Why CSP Matters
- XSS prevention: CSP blocks the execution of injected scripts, neutralizing the most common web vulnerability category.
- Supply chain protection: If a third-party CDN is compromised, CSP prevents the modified script from exfiltrating data to unauthorized domains.
- Compliance: Security frameworks (SOC 2, PCI-DSS) and vendor security reviews expect CSP as a standard security measure.
- Data exfiltration prevention: The
connect-srcdirective prevents malicious scripts from sending stolen data to attacker servers. - GDPR technical measure: CSP is considered an “appropriate technical measure” under GDPR Article 32, helping meet the requirement for data protection by design.
How CSP Prevents XSS Attacks
Blocking Malicious Execution
Cross-Site Scripting (XSS) occurs when an attacker tricks the browser into executing malicious JavaScript. A strong CSP mitigates this by:
- Blocking inline scripts: Injected
<script>malicious()</script>tags are dropped because inline scripts are not in the whitelist. - Restricting domains: Only scripts from explicitly whitelisted origins can execute. A script loaded from
evil.comis blocked automatically. - Preventing eval(): Dynamic code execution functions like
eval()andnew Function()are blocked by default. - Blocking data exfiltration: Even if a script executes,
connect-srcprevents it from sending data to unauthorized servers.
Don't Use unsafe-inline
'unsafe-inline' to your CSP defeats its primary purpose. Use nonces or hashes to whitelist specific inline scripts instead.CSP Directives Reference
Understanding Directives
| Directive | Controls | Example |
|---|---|---|
| default-src | Fallback for all resource types | 'self' |
| script-src | JavaScript files | 'self' https://cdn.example.com |
| style-src | CSS stylesheets | 'self' 'unsafe-inline' |
| img-src | Images | 'self' data: https://images.example.com |
| connect-src | XHR, Fetch, WebSocket | 'self' https://api.example.com |
| font-src | Web fonts | 'self' https://fonts.gstatic.com |
| frame-ancestors | Who can embed your page | 'none' |
| base-uri | Restricts <base> tag URLs | 'self' |
| form-action | Where forms can submit | 'self' |
| report-uri | Where to send violation reports | /csp-report-endpoint |
Build a Content Security Policy for your site interactively.
Open CSP GeneratorReal-World Examples
The absence of CSP has contributed to major breaches:
- British Airways (2018): No CSP allowed a Magecart script to capture 380,000 payment cards from the checkout page. CSP would have blocked the unauthorized script and prevented data exfiltration.
- Ticketmaster (2018): A compromised third-party chatbot widget injected a payment skimmer. CSP with SRI would have blocked the modified script from executing.
- Newegg (2018): Magecart attackers injected a 15-line payment skimmer that ran undetected for a month. A script-src CSP directive would have blocked the unauthorized domain.
CSP Would Have Prevented All Three
script-src and connect-src directives would have blocked the attack at the browser level.How to Check Your CSP
- Run a header scan: Use the Security Headers Checker to see if your CSP header is present and review its directives.
- Check DevTools: Open Console tab, CSP violations appear as errors with the blocked resource URL and the violated directive.
- Set up violation reporting: Add a
report-uriorreport-todirective to collect violation data in production. - Use CSP Evaluator: Google's CSP Evaluator tool analyzes your policy for common weaknesses and recommendations.
How to Implement CSP
Deployment Strategy
- Start with Report-Only: Use
Content-Security-Policy-Report-Onlyfirst to log violations without blocking content. Monitor for at least one week. - Build your policy: Use the CSP Generator to build a policy interactively.
- Deploy incrementally: Start with
default-src 'self'and add allowed sources as needed based on violation reports. - Replace inline scripts with nonces: Instead of
unsafe-inline, generate a random nonce for each request:
Nonce-Based CSP
Content-Security-Policy: script-src 'nonce-abc123'
<script nonce="abc123">
// This script executes because the nonce matches
</script>
<script>
// This script is BLOCKED, no matching nonce
</script>Nginx Configuration
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://api.example.com; frame-ancestors 'none'" always;Best Practices
- Start with Report-Only mode, deploy in report-only for at least a week before enforcing to avoid breaking functionality.
- Use nonces over domain whitelists, cryptographic nonces are more secure because domain whitelists can be bypassed if a CDN is compromised.
- Set frame-ancestors, use
frame-ancestors 'none'to replace X-Frame-Options with the modern CSP-based equivalent. - Monitor violations in production, set up a reporting endpoint to catch policy violations. This helps you detect both misconfiguration and attempted attacks.
- Restrict connect-src, this directive prevents data exfiltration. Only whitelist API endpoints that your application actually uses.
- Combine with other security headers, CSP works best alongside HSTS, Referrer-Policy, and Permissions-Policy.
Common Mistakes
- Using 'unsafe-inline': This allows any inline script to execute, completely defeating CSP's XSS protection. Use nonces or hashes instead.
- Using 'unsafe-eval': Required by some older frameworks, but opens the door to code injection via
eval(). Migrate to frameworks that do not require it. - Whitelisting entire CDN domains: If you whitelist
https://cdn.jsdelivr.net, an attacker can host malicious code on the same CDN. Use SRI hashes alongside domain whitelists. - Not setting default-src: Without a
default-srcfallback, resource types without specific directives have no restrictions. - Deploying without Report-Only first: Enforcing an untested CSP can break your entire site. Always test in report-only mode first.
- Forgetting about WebSockets: If your app uses WebSockets, you need to include the WebSocket URL in
connect-src.
Conclusion
Content Security Policy is the single most effective browser-level defense against XSS and supply-chain attacks. It is not a replacement for secure coding practices, but it catches the attacks that slip through. Every website should have a CSP, the question is not whether to implement one, but how strict to make it.
Start with Report-Only mode, build your policy incrementally using the CSP Generator, and monitor violations in production. Combined with other security headers, CSP creates a robust defense-in-depth strategy.
Scan Your Website
Related Guides
Frequently Asked Questions
Does CSP replace input validation?+
Why is my third-party widget breaking with CSP?+
What does 'unsafe-inline' do?+
How do I test CSP without breaking my site?+
Can CSP protect against Magecart attacks?+
Do I need different CSP for different pages?+
Verify Your CSP Configuration
A misconfigured CSP can break your site or leave it vulnerable. Scan your domain to check if your policy is correctly structured.
For deeper runtime checks, run the full privacy audit →