Content Security Policy (CSP) is a powerful defense against XSS and injection attacks.
Basic CSP Header#
1// Express middleware
2app.use((req, res, next) => {
3 res.setHeader(
4 'Content-Security-Policy',
5 "default-src 'self'; script-src 'self'; style-src 'self'"
6 );
7 next();
8});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, fetch sources
frame-src - iframe sources
object-src - plugins (Flash, etc.)
base-uri - <base> tag restrictions
form-action - Form submission targets
frame-ancestors - Who can embed this page
Practical CSP Examples#
1// Strict CSP for modern apps
2const csp = [
3 "default-src 'self'",
4 "script-src 'self'",
5 "style-src 'self' 'unsafe-inline'", // Often needed for CSS-in-JS
6 "img-src 'self' data: https:",
7 "font-src 'self'",
8 "connect-src 'self' https://api.example.com",
9 "frame-ancestors 'none'",
10 "base-uri 'self'",
11 "form-action 'self'",
12].join('; ');
13
14// With external resources
15const cspWithExternal = [
16 "default-src 'self'",
17 "script-src 'self' https://cdn.jsdelivr.net",
18 "style-src 'self' https://fonts.googleapis.com",
19 "font-src 'self' https://fonts.gstatic.com",
20 "img-src 'self' data: https://images.example.com",
21 "connect-src 'self' https://api.example.com wss://ws.example.com",
22].join('; ');Nonce-Based CSP#
1import crypto from 'crypto';
2
3app.use((req, res, next) => {
4 // Generate unique nonce per request
5 const nonce = crypto.randomBytes(16).toString('base64');
6 res.locals.nonce = nonce;
7
8 res.setHeader(
9 'Content-Security-Policy',
10 `script-src 'self' 'nonce-${nonce}'`
11 );
12
13 next();
14});
15
16// In template
17<script nonce="<%= nonce %>">
18 // Inline script allowed with matching nonce
19</script>Hash-Based CSP#
1import crypto from 'crypto';
2
3// Hash inline scripts at build time
4const inlineScript = "console.log('Hello')";
5const hash = crypto
6 .createHash('sha256')
7 .update(inlineScript)
8 .digest('base64');
9
10// CSP header
11`script-src 'self' 'sha256-${hash}'`
12
13// In HTML
14<script>console.log('Hello')</script>Next.js CSP#
1// next.config.js
2const cspHeader = `
3 default-src 'self';
4 script-src 'self' 'unsafe-eval' 'unsafe-inline';
5 style-src 'self' 'unsafe-inline';
6 img-src 'self' blob: data:;
7 font-src 'self';
8 object-src 'none';
9 base-uri 'self';
10 form-action 'self';
11 frame-ancestors 'none';
12`;
13
14module.exports = {
15 async headers() {
16 return [
17 {
18 source: '/(.*)',
19 headers: [
20 {
21 key: 'Content-Security-Policy',
22 value: cspHeader.replace(/\n/g, ''),
23 },
24 ],
25 },
26 ];
27 },
28};CSP Reporting#
1// Report-only mode (doesn't block, just reports)
2res.setHeader(
3 'Content-Security-Policy-Report-Only',
4 "default-src 'self'; report-uri /api/csp-report"
5);
6
7// Reporting endpoint
8app.post('/api/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
9 const report = req.body['csp-report'];
10
11 console.log('CSP Violation:', {
12 blockedUri: report['blocked-uri'],
13 violatedDirective: report['violated-directive'],
14 documentUri: report['document-uri'],
15 });
16
17 res.status(204).end();
18});
19
20// Modern reporting API
21`report-to csp-endpoint`
22
23// Report-To header
24res.setHeader('Report-To', JSON.stringify({
25 group: 'csp-endpoint',
26 max_age: 10886400,
27 endpoints: [{ url: '/api/csp-report' }],
28}));Deployment Strategy#
1// 1. Start with report-only
2'Content-Security-Policy-Report-Only': "default-src 'self'"
3
4// 2. Analyze reports, adjust policy
5
6// 3. Gradually tighten
7// Week 1: Allow everything you need
8// Week 2: Remove 'unsafe-inline' for scripts
9// Week 3: Remove 'unsafe-eval'
10
11// 4. Enable enforcement
12'Content-Security-Policy': "final-policy-here"
13
14// 5. Keep report-uri for monitoringCommon Issues#
1// ❌ Error: Inline event handlers blocked
2<button onclick="doSomething()">Click</button>
3
4// ✅ Use addEventListener instead
5document.querySelector('button').addEventListener('click', doSomething);
6
7// ❌ Error: eval() blocked
8eval(userCode);
9
10// ✅ Avoid eval, or add 'unsafe-eval' (not recommended)
11
12// ❌ Error: Inline styles blocked
13<div style="color: red">Text</div>
14
15// ✅ Use classes or 'unsafe-inline' for styles
16// (Less risky than script 'unsafe-inline')CSP significantly reduces XSS risk when implemented properly.