Content Security Policy (CSP) is a browser security mechanism that controls which resources a web page is allowed to load. By declaring a policy via HTTP header, you tell the browser exactly which scripts, styles, images, fonts, and connections are permitted. Anything not explicitly allowed is blocked.
CSP is one of the most effective defenses against Cross-Site Scripting (XSS) and data injection attacks.
How CSP Works#
CSP is delivered as an HTTP response header:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com
When the browser receives this header, it enforces the policy for the entire page. If a script tries to load from an unauthorized origin, the browser blocks it and logs a violation.
Key Directives#
| Directive | Controls |
|---|---|
default-src | Fallback for all resource types |
script-src | JavaScript sources |
style-src | CSS sources |
img-src | Image sources |
connect-src | XHR, fetch, WebSocket destinations |
font-src | Font file sources |
frame-src | Iframe sources |
media-src | Audio and video sources |
object-src | Plugin sources (Flash, Java) |
base-uri | Allowed <base> URLs |
form-action | Form submission targets |
frame-ancestors | Who can embed this page (replaces X-Frame-Options) |
Example Policies#
Strict policy — only allow resources from the same origin:
Content-Security-Policy: default-src 'self'
Allow a CDN for scripts and styles:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' https://cdn.example.com
Allow inline styles but not inline scripts:
Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'
Nonces vs Hashes#
Instead of allowing 'unsafe-inline' (which defeats much of CSP’s purpose), use nonces or hashes to authorize specific inline scripts:
Nonce approach — generate a random value per request:
Content-Security-Policy: script-src 'nonce-abc123'
<script nonce="abc123">
// This script is allowed because the nonce matches
</script>
Hash approach — allow a specific script by its SHA-256 hash:
Content-Security-Policy: script-src 'sha256-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc='
Nonces are generally preferred because they don’t require recomputing hashes when script content changes.
Report-Only Mode#
Test a policy without enforcing it:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-reports
The browser logs violations but doesn’t block anything. This lets you identify what would break before deploying the policy.
Common Mistakes#
- Using
'unsafe-inline'for scripts — this allows any inline script, including injected XSS payloads. Use nonces or hashes instead. - Using
'unsafe-eval'— allowseval(),Function(), and other dynamic code execution. Avoid unless absolutely necessary. - Overly broad wildcards —
script-src *orscript-src *.googleapis.commay include domains that host user-controlled content, creating bypass vectors. - Forgetting
default-src— if you don’t set a fallback, undeclared resource types have no restrictions. - Not using
object-src 'none'— Flash and other plugins are legacy attack vectors. Block them explicitly.
A Starter Policy#
A reasonable starting point for most sites:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
Then tighten it by removing 'unsafe-inline' for styles and adding nonces for any inline scripts.
For deeper analysis of CSP headers — parsing, finding bypasses, and scoring policies at scale — see my csp-toolkit post and the csp-toolkit library.
Real-World Implementation Examples#
Let me show you how to actually implement CSP in different frameworks, because the theory only gets you so far.
Python/Flask Implementation#
from flask import Flask, render_template
import secrets
app = Flask(__name__)
def generate_nonce():
return secrets.token_urlsafe(16)
@app.before_request
def add_csp_header():
nonce = generate_nonce()
g.csp_nonce = nonce
csp_policy = (
f"default-src 'self'; "
f"script-src 'self' 'nonce-{nonce}'; "
f"style-src 'self' 'unsafe-inline'; "
f"img-src 'self' data: https:; "
f"font-src 'self'; "
f"object-src 'none'; "
f"frame-ancestors 'none'"
)
@after_this_request
def add_header(response):
response.headers['Content-Security-Policy'] = csp_policy
return response
@app.route('/')
def index():
return render_template('index.html', csp_nonce=g.csp_nonce)
<!-- Template with nonce -->
<script nonce="{{ csp_nonce }}">
// This script is allowed
console.log('CSP is working!');
</script>
Node.js/Express Implementation#
const express = require('express');
const crypto = require('crypto');
const app = express();
// CSP middleware
app.use((req, res, next) => {
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.cspNonce = nonce;
const csp = [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}'`,
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"object-src 'none'",
"frame-ancestors 'none'"
].join('; ');
res.setHeader('Content-Security-Policy', csp);
next();
});
app.get('/', (req, res) => {
res.send(`
<html>
<head>
<script nonce="${res.locals.cspNonce}">
console.log('Protected by CSP');
</script>
</head>
<body>CSP Demo</body>
</html>
`);
});
WordPress/PHP Implementation#
function add_csp_header() {
$nonce = base64_encode(random_bytes(16));
$csp_policy = sprintf(
"default-src 'self'; script-src 'self' 'nonce-%s'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; object-src 'none'; frame-ancestors 'none'",
$nonce
);
header("Content-Security-Policy: $csp_policy");
// Make nonce available to templates
global $csp_nonce;
$csp_nonce = $nonce;
}
add_action('init', 'add_csp_header');
// In templates
echo "<script nonce='$csp_nonce'>console.log('CSP protected');</script>";
Advanced CSP Techniques#
Strict CSP for Modern Applications#
For SPAs and modern web apps, you can use a much stricter policy:
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
require-trusted-types-for 'script';
The 'strict-dynamic' directive allows nonce-authorized scripts to load additional scripts, perfect for modern bundlers and dynamic imports.
CSP for API Endpoints#
Even your APIs should have CSP headers to prevent data injection:
Content-Security-Policy: default-src 'none'; frame-ancestors 'none';
This blocks everything except the response data itself.
Handling Third-Party Scripts#
Real applications need analytics, ads, and other third-party scripts. Here’s how to handle them safely:
# Allowlist approach for third parties
TRUSTED_SCRIPT_SOURCES = [
'https://www.google-analytics.com',
'https://www.googletagmanager.com',
'https://js.stripe.com'
]
csp_policy = (
f"default-src 'self'; "
f"script-src 'self' 'nonce-{nonce}' {' '.join(TRUSTED_SCRIPT_SOURCES)}; "
f"connect-src 'self' https://api.stripe.com; "
f"frame-src https://js.stripe.com"
)
CSP Bypass Techniques (And How to Prevent Them)#
Understanding how attackers bypass CSP helps you write better policies.
JSONP Bypasses#
If you allow 'unsafe-eval' or certain domains, attackers can use JSONP:
<!-- Attacker payload -->
<script src="https://trusted-api.com/callback?callback=alert"></script>
Prevention: Never use 'unsafe-eval' and carefully vet allowed script domains.
Base Tag Injection#
If you don’t set base-uri, attackers can inject base tags to redirect relative URLs:
<base href="https://evil.com/">
<script src="/malicious.js"></script> <!-- Loads from evil.com -->
Prevention: Always include base-uri 'self' in your policy.
Angular.js Bypasses#
Older versions of Angular.js can be exploited if you allow 'unsafe-eval':
<div ng-app ng-csp>
{{$on.constructor('alert(1)')()}}
</div>
Prevention: Update Angular.js and never use 'unsafe-eval'.
CSP Monitoring and Reporting#
Setting up proper reporting helps you catch both violations and attacks.
Report Collection Endpoint#
from flask import Flask, request
import json
import logging
app = Flask(__name__)
@app.route('/csp-report', methods=['POST'])
def csp_report():
try:
report = request.get_json()
# Log the violation
logging.warning(f"CSP Violation: {json.dumps(report)}")
# Check for potential attacks
violated_directive = report.get('csp-report', {}).get('violated-directive', '')
if 'script-src' in violated_directive:
# Potential XSS attempt
logging.error(f"Possible XSS attempt: {report}")
return '', 204
except Exception as e:
logging.error(f"CSP report processing error: {e}")
return '', 400
Report-Only Testing#
Before deploying strict CSP, test with report-only mode:
# Phase 1: Report-only mode
csp_policy_report = "default-src 'self'; script-src 'self'; report-uri /csp-report"
response.headers['Content-Security-Policy-Report-Only'] = csp_policy_report
# Phase 2: Enforce mode (after fixing violations)
csp_policy_enforce = "default-src 'self'; script-src 'self'"
response.headers['Content-Security-Policy'] = csp_policy_enforce
Framework-Specific CSP Patterns#
React Applications#
// React with CSP nonces
function App({ cspNonce }) {
useEffect(() => {
// For inline scripts that need CSP nonce
const script = document.createElement('script');
script.nonce = cspNonce;
script.textContent = `
// Analytics or other necessary inline code
console.log('React app with CSP');
`;
document.head.appendChild(script);
}, [cspNonce]);
return <div>CSP Protected React App</div>;
}
Vue.js Applications#
// Vue.js CSP configuration
const app = createApp({
mounted() {
// Use CSP-safe patterns
this.$nextTick(() => {
// Avoid eval-based templates in production
console.log('Vue with CSP');
});
}
});
// CSP-friendly Vue config
app.config.globalProperties.$cspNonce = window.cspNonce;
Progressive CSP Implementation#
Don’t try to implement perfect CSP on day one. Here’s a realistic rollout strategy:
Week 1: Baseline Policy#
Content-Security-Policy-Report-Only: default-src 'self' 'unsafe-inline' 'unsafe-eval'; report-uri /csp-report
Week 2: Remove unsafe-eval#
Content-Security-Policy-Report-Only: default-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; report-uri /csp-report
Week 3: Add nonces for scripts#
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self' 'unsafe-inline'; report-uri /csp-report
Week 4: Enforce mode#
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; object-src 'none'; frame-ancestors 'none'
Frequently Asked Questions#
Does CSP really stop XSS attacks?#
CSP is incredibly effective against XSS, but it’s not magic. A properly configured CSP with nonces or strict-dynamic can block most XSS payloads. However, CSP bypasses exist, especially with overly permissive policies. Think of CSP as a very strong safety net, not a complete solution.
Should I use nonces or hashes for inline scripts?#
Nonces are usually better because you don’t have to recalculate hashes when scripts change. Hashes work well for static content that rarely changes. For dynamic applications, nonces are more practical.
Can CSP break my site’s functionality?#
Yes, CSP can definitely break things if implemented too aggressively. Always start with report-only mode and monitor violations for at least a week before enforcing. Common breaking points include inline scripts, third-party widgets, and dynamic script loading.
How do I handle Google Analytics with CSP?#
Add Google’s domains to your script-src and connect-src directives:
script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com;
connect-src 'self' https://www.google-analytics.com;
What’s the difference between CSP and HTTPS?#
HTTPS encrypts data in transit but doesn’t prevent malicious scripts from running. CSP controls which scripts can execute but doesn’t encrypt anything. You need both for comprehensive security.
Can I use CSP with a CDN?#
Absolutely, but you’ll need to include your CDN domains in the appropriate directives. For example:
script-src 'self' https://cdn.jsdelivr.net; font-src 'self' https://fonts.googleapis.com;
Related Security Topics#
🎯 XSS Prevention & CSP#
- CSP Toolkit: Analyze Headers at Scale - Python library for CSP analysis and bypass detection
- Python Security Guide - Prevent XSS in Python applications
- Common Weakness Enumeration - CWE-79: Cross-Site Scripting classification
🛠️ Hands-On Practice#
- Security Playground - Interactive XSS demos and CSP testing
- CSP Toolkit - Test and analyze your CSP headers
🔐 Advanced Security#
- SSRF Prevention Guide - Server-side request forgery defense
- SSRF Attack Vectors - Understanding server-side attacks
📚 Python Developers#
- Python Secure Coding - Comprehensive vulnerability prevention
- Getting Started with Requests - Secure HTTP client usage