What Is Content Security Policy?
Content Security Policy (CSP) is an HTTP security header that tells browsers which sources of content are allowed to load on your page. It is the single most effective defense against Cross-Site Scripting (XSS) attacks.
Without CSP, a browser will execute any JavaScript it encounters on your page, whether you put it there or an attacker injected it. With CSP, the browser blocks anything not explicitly allowed by your policy.
Why CSP Matters
XSS is the most common web vulnerability. It lets attackers: - Steal session cookies and hijack user accounts - Redirect users to phishing pages - Inject cryptominers that use your visitors' CPUs - Deface your website with malicious content - Exfiltrate data from forms and pages
CSP does not just detect XSS — it prevents it at the browser level.
CSP Basics
A CSP header looks like this:
Content-Security-Policy: directive1 source1 source2; directive2 source3;
Key Directives
| Directive | Controls | Example |
|---|---|---|
default-src | Fallback for all resource types | 'self' |
script-src | JavaScript files | 'self' cdn.example.com |
style-src | CSS stylesheets | 'self' 'unsafe-inline' |
img-src | Images | 'self' data: *.cloudinary.com |
font-src | Web fonts | 'self' fonts.gstatic.com |
connect-src | AJAX, WebSocket, fetch | 'self' api.example.com |
frame-src | Iframes | 'none' |
object-src | Flash, Java applets | 'none' |
base-uri | Base URL for relative URLs | 'self' |
form-action | Form submission targets | 'self' |
Source Values
| Value | Meaning |
|---|---|
'self' | Same origin only |
'none' | Block everything |
'unsafe-inline' | Allow inline scripts/styles (weakens CSP) |
'unsafe-eval' | Allow eval() (weakens CSP) |
'nonce-abc123' | Allow specific inline scripts with matching nonce |
*.example.com | Allow from subdomains |
https: | Allow any HTTPS source |
data: | Allow data: URIs |
Step-by-Step Implementation
Step 1: Start with Report-Only Mode
Do not jump straight to enforcing CSP. Start by monitoring what would be blocked:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
This logs violations without blocking anything. Check your server logs or a CSP reporting service to see what would break.
Step 2: Build Your Base Policy
Start strict and add exceptions:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
Step 3: Add Your CDNs and Services
If you use Google Fonts, Analytics, Stripe, etc., add them:
Content-Security-Policy:
default-src 'self';
script-src 'self' js.stripe.com www.googletagmanager.com;
style-src 'self' 'unsafe-inline' fonts.googleapis.com;
img-src 'self' data: *.google-analytics.com;
font-src 'self' fonts.gstatic.com;
connect-src 'self' api.stripe.com *.google-analytics.com;
frame-src js.stripe.com;
object-src 'none';
base-uri 'self';
Step 4: Handle Inline Scripts with Nonces
Instead of using 'unsafe-inline' (which weakens CSP), use nonces:
<!-- Server generates a unique nonce per request -->
<script nonce="a1b2c3d4e5">
console.log('This is allowed');
</script>
Content-Security-Policy: script-src 'self' 'nonce-a1b2c3d4e5';
The nonce must be cryptographically random and different on every page load.
Step 5: Switch to Enforcement
Once you have confirmed no legitimate resources are blocked, remove Report-Only:
Content-Security-Policy: default-src 'self'; ...
Server Configuration Examples
Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none';" always;
Apache
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none';"
Next.js (next.config.js)
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none';"
}
];
Common Mistakes
- 1Using `'unsafe-inline'` and `'unsafe-eval'` everywhere — This basically disables CSP protection. Use nonces instead.
- 2Setting `default-src *` — This allows everything and defeats the purpose.
- 3Forgetting `object-src 'none'` — Flash and Java applets are attack vectors.
- 4Not testing in report-only mode first — You will break your site.
- 5Setting CSP once and forgetting — New third-party scripts need to be added to the policy.
Check Your CSP
Run a ZeriFlow scan to instantly check if your website has a Content Security Policy header and how well it is configured. The scan evaluates your CSP along with 80+ other security checks.
Conclusion
Content Security Policy is the most powerful browser-side defense against XSS. Start with report-only mode, build your policy gradually, use nonces instead of unsafe-inline, and test regularly. Your users' security depends on it.