Content Security Policy (CSP) is a powerful HTTP header that helps prevent cross-site scripting (XSS), clickjacking, and other code injection attacks. By defining which resources can load, CSP provides an additional layer of security for web applications.
Understanding CSP
CSP works by specifying allowed sources for content types like scripts, styles, images, and more. If a resource loads from a disallowed source, the browser blocks it.
Basic CSP Header
``http Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com
## CSP Directives
### Core Directives
``http
# Allow resources from same origin only
Content-Security-Policy: default-src 'self';
# Allow scripts from specific sources
Content-Security-Policy: script-src 'self' https://trusted.cdn.com;
# Allow stylesheets with inline styles
Content-Security-Policy: style-src 'self' 'unsafe-inline';
# Allow images from anywhere
Content-Security-Policy: img-src * data:;
# Allow fonts
Content-Security-Policy: font-src 'self' https://fonts.gstatic.com;
# Connect to APIs
Content-Security-Policy: connect-src 'self' https://api.example.com;
# Allow media
Content-Security-Policy: media-src 'self' https://media.example.com;
# Allow objects (plugins, embeds)
Content-Security-Policy: object-src 'none';
# Frame restrictions
Content-Security-Policy: frame-src 'self' https://embed.example.com;
Content-Security-Policy: frame-ancestors 'none'; # Prevent embeddingImplementing CSP
Express.js Implementation
const express = require('express');
const helmet = require('helmet');
const app = express();
// Basic CSP with helmet
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
scriptSrc: ["'self'", "https://cdn.example.com"],
imgSrc: ["'self'", "data:", "https:"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
connectSrc: ["'self'", "https://api.example.com"],
mediaSrc: ["'self'"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
frameSrc: ["'none'"],
upgradeInsecureRequests: []
}
}));
// Custom CSP middleware
function customCSP(directives) {
return (req, res, next) => {
const policy = Object.entries(directives)
.map(([key, values]) => `${key} ${values.join(' ')}`)
.join('; ');
res.setHeader('Content-Security-Policy', policy);
next();
};
}
app.use(customCSP({
'default-src': ["'self'"],
'script-src': ["'self'", "'unsafe-eval'", "'unsafe-inline'", "https://cdn.jsdelivr.net"],
'style-src': ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
'img-src': ["'self'", "data:", "https:"],
'font-src': ["'self'", "https://fonts.gstatic.com"]
}));Next.js Implementation
// next.config.js
const ContentSecurityPolicy = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: blob: https:;
connect-src 'self' https://api.example.com;
media-src 'self';
object-src 'none';
frame-ancestors 'none';
`;
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim()
},
{
key: 'X-Frame-Options',
value: 'DENY'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
}
]
}
];
}
};Handling Inline Scripts and Styles
Nonce-Based CSP
const crypto = require('crypto');
app.use((req, res, next) => {
// Generate nonce for inline scripts
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.nonce = nonce;
// Set CSP with nonce
res.setHeader(
'Content-Security-Policy',
`script-src 'self' 'nonce-${nonce}' https://cdn.example.com;`
);
next();
});
// Template rendering
app.get('/', (req, res) => {
res.render('index', {
nonce: res.locals.nonce,
message: 'Hello World'
});
});``html <!-- Template with nonce --> <script nonce="<%= nonce %>"> console.log('Inline script with nonce'); </script>
<script nonce="<%= nonce %>"> // jQuery or other inline code $(document).ready(function() { // ... }); </script>
### Hash-Based CSP
const crypto = require('crypto');
function generateHash(content) { return crypto .createHash('sha256') .update(content) .digest('base64'); }
// Pre-compute hashes for inline scripts const inlineScriptHashes = [ generateHash('console.log("hello")'), generateHash('alert("welcome")') ];
app.use((req, res, next) => { const hashDirective = inlineScriptHashes .map(hash => 'sha256-${hash}') .join(' ');
res.setHeader( 'Content-Security-Policy', script-src 'self' 'nonce-${res.locals.nonce}' ${hashDirective} );
next(); });
## CSP Violation Reporting
### Report-To Header
app.use((req, res, next) => { res.setHeader('Report-To', JSON.stringify({ group: 'csp-endpoint', max_age: 10886400, endpoints: [{ url: '/csp-violation-report' }] }));
res.setHeader( 'Content-Security-Policy', default-src 'self'; report-to csp-endpoint; report-uri /csp-violation-report );
next(); });
// Violation report endpoint app.post('/csp-violation-report', express.json({ type: 'application/csp-report' }), (req, res) => { const report = req.body['csp-report'];
console.error('CSP Violation:', { violatedDirective: report['violated-directive'], blockedURI: report['blocked-uri'], originalPolicy: report['original-policy'], documentURI: report['document-uri'], referrer: report['referrer'], statusCode: report['status-code'] });
// Log to monitoring service logger.error('CSP Violation', report);
res.sendStatus(204); });
### Report-Only Mode
// Test CSP without blocking app.use((req, res, next) => { // Report-only mode res.setHeader( 'Content-Security-Policy-Report-Only', default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; report-uri /csp-violation-report );
// Enforce mode (after testing) // res.setHeader( // 'Content-Security-Policy', // default-src 'self'; script-src 'self'; report-uri /csp-violation-report // );
next(); });
## Common CSP Patterns
### Strict Policy
``http
Content-Security-Policy:
default-src 'none';
script-src 'self';
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
form-action 'self';
frame-ancestors 'none';
base-uri 'self';
block-all-mixed-content;
upgrade-insecure-requests;Progressive Policy
``http
Start permissive
Content-Security-Policy: default-src *; script-src * 'unsafe-inline' 'unsafe-eval'
Gradually tighten
Content-Security-Policy: default-src 'self' *.example.com; script-src 'self' 'unsafe-inline' cdn.example.com
Eventually reach strict
Content-Security-Policy: default-src 'self'; script-src 'self'
### Development vs Production
const isDevelopment = process.env.NODE_ENV !== 'production';
const getCSP = () => { if (isDevelopment) { // Permissive for development return { 'default-src': ["'self'"], 'script-src': ["'self'", "'unsafe-eval'", "'unsafe-inline'"], 'style-src': ["'self'", "'unsafe-inline'"], 'connect-src': ["'self'", "http://localhost:*"] }; } else { // Strict for production return { 'default-src': ["'self'"], 'script-src': ["'self'", "'nonce-' + getNonce()"], 'style-src': ["'self'", "'unsafe-inline'"], 'connect-src': ["'self'", "https://api.example.com"], 'report-uri': ['/csp-violation-report'] }; } };
## Debugging CSP
### Browser DevTools
``javascript
// Console shows CSP violations
// Refused to load inline script because it violates the following CSP directive:
// "script-src 'self' 'nonce-abc123'"Testing Tools
# Test CSP with CSP Evaluator
npx csp-evaluator https://example.com
# Online tools
# https://csp-evaluator.withgoogle.com/
# https://securityheaders.com/Best Practices
- 1Start with Report-Only: Test CSP before enforcing
- 2Use Nonce for Inline: Prefer nonce over unsafe-inline
- 3Minimize Directives: Only allow what you need
- 4Monitor Violations: Set up reporting and logging
- 5Gradual Tightening: Start permissive, tighten over time
- 6Separate Policies: Different policies for different routes
- 7Document Sources: Keep track of why each source is allowed
- 8Regular Audits: Review and update CSP regularly
Conclusion
Content Security Policy is a powerful defense against XSS and injection attacks. Implement it gradually, starting with report-only mode, and progressively tighten your policy based on violation reports. A well-configured CSP significantly improves your application's security posture.