Security
Content Security Policy
Prevent XSS and data injection attacks
Intermediate
security xss headers csp
Definition
Content Security Policy (CSP) is a security layer that helps prevent Cross-Site Scripting (XSS) and data injection attacks by specifying which dynamic resources are allowed to load. It provides a way for administrators to control resources the browser is allowed to load for a given page.
How CSP Works
Basic Header
Content-Security-Policy: default-src 'self'
This tells the browser:
- Only load resources from the same origin
- Block inline scripts
- Block eval()
- Block external resources
Report-Only Mode
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
- Violations are reported but not blocked
- Useful for testing before enforcing
CSP Directives
Script Sources
Content-Security-Policy:
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
media-src 'self';
object-src 'none';
frame-src 'self' https://trusted-site.com;
Common Directives
default-src: Fallback for other directives
script-src: JavaScript sources
style-src: CSS sources
img-src: Image sources
font-src: Font sources
connect-src: XHR, WebSocket, EventSource
media-src: Video/audio sources
frame-src: Frame/iframe sources
object-src: Flash, plugins
base-uri: Base URL for relative URLs
form-action: Form submission targets
upgrade-insecure-requests: Force HTTPS
Source Values
Keywords
'self': Same origin only
'none': Block all
'unsafe-inline': Allow inline scripts/styles
'unsafe-eval': Allow eval() and new Function()
'unsafe-hashes': Allow specific inline scripts via hash
'strict-dynamic': Trust scripts loaded by trusted scripts
'nonce-<value>': Specific inline script allowed
Examples
Content-Security-Policy:
# Allow same origin + specific CDN
script-src 'self' https://cdn.jsdelivr.net;
# Block all plugins
object-src 'none';
# Allow inline styles (needed for some frameworks)
style-src 'self' 'unsafe-inline';
# Data URIs for images
img-src 'self' data:;
Implementing CSP
Strict Policy (Recommended)
Content-Security-Policy:
default-src 'none';
script-src 'self';
style-src 'self';
img-src 'self';
font-src 'self';
connect-src 'self';
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
For React/Vue/Angular Apps
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
font-src 'self' data:;
connect-src 'self' https://api.example.com;
Inline Scripts
<!-- Nonce approach (preferred) -->
<meta http-equiv="Content-Security-Policy"
content="script-src 'nonce-abc123'">
<script nonce="abc123">
console.log('Allowed');
</script>
<script>
console.log('Blocked - no nonce');
</script>
<!-- Hash approach -->
<meta http-equiv="Content-Security-Policy"
content="script-src 'sha256-abc...def='">
<script>
console.log('Allowed if hash matches');
</script>
Server Configuration
Express.js
const helmet = require('helmet');
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.example.com"],
},
}));
Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
HTML Meta Tag
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'">
CSP Violations
Browser Console
Refused to load the script 'https://evil.com/malware.js'
because it violates the following Content Security Policy directive:
"script-src 'self'".
Reporting
Content-Security-Policy:
default-src 'self';
report-uri /csp-report;
report-to csp-endpoint;
# Modern approach with Reporting API
Report-To: {
"group": "csp-endpoint",
"max_age": 10886400,
"endpoints": [{
"url": "https://example.com/csp-report"
}]
}
Report Handler
// Server-side report collector
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
const report = req.body['csp-report'];
console.log('CSP Violation:', {
documentUri: report['document-uri'],
blockedUri: report['blocked-uri'],
violatedDirective: report['violated-directive'],
originalPolicy: report['original-policy']
});
res.status(204).send();
});
Best Practices
1. Start with Report-Only
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
Monitor reports for a week before enforcing.
2. Use Nonces for Inline Scripts
// Generate unique nonce per request
const nonce = crypto.randomBytes(16).toString('base64');
// Pass to template
res.render('page', { nonce });
<script nonce="<%= nonce %>">
// This inline script is allowed
</script>
3. Avoid ‘unsafe-inline’ and ‘unsafe-eval’
❌ script-src 'self' 'unsafe-inline' 'unsafe-eval';
✅ script-src 'self' 'nonce-abc123';
✅ script-src 'self' 'strict-dynamic';
4. Frame Ancestors
# Prevent clickjacking
frame-ancestors 'none';
# Or allow specific sites
frame-ancestors 'self' https://trusted-site.com;
5. Upgrade Insecure Requests
Content-Security-Policy: upgrade-insecure-requests;
# Forces all http:// to https://
Common Issues
Third-Party Scripts
# Google Analytics
script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com;
img-src 'self' https://www.google-analytics.com;
# Stripe
script-src 'self' https://js.stripe.com;
frame-src 'self' https://js.stripe.com https://hooks.stripe.com;
connect-src 'self' https://api.stripe.com;
Web Workers
worker-src 'self' blob:;
WebSockets
connect-src 'self' wss://socket.example.com;
Key Takeaway
CSP is a powerful defense against XSS attacks. Start with report-only mode, use nonces for inline scripts instead of ‘unsafe-inline’, and whitelist only necessary domains. Regularly review violation reports to refine your policy. CSP works best when combined with other security headers like X-Frame-Options and HSTS.