CSP Basics
Content Security Policy
What is CSP?
Content Security Policy restricts which resources can be loaded and executed.
Basic Configuration
// Simple CSP
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'"
);
next();
});
// Multiple directives
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' https://trusted.com; style-src 'self' 'unsafe-inline'"
);
next();
});
Common Directives
| Directive | Controls |
|---|---|
| default-src | Default for all resource types |
| script-src | JavaScript files |
| style-src | CSS files |
| img-src | Images |
| font-src | Fonts |
| connect-src | AJAX, WebSocket |
| frame-src | Iframes |
| object-src | Plugins |
| media-src | Audio/Video |
Source Values
| Value | Description |
|---|---|
| 'self' | Same origin |
| 'none' | Block all |
| 'unsafe-inline' | Allow inline |
| 'unsafe-eval' | Allow eval() |
| https: | HTTPS only |
| data: | data: URIs |
| blob: | blob: URIs |
| domain.com | Specific domain |
Example Policies
// Strict policy
"default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self';"
// Relaxed policy
"default-src 'self' https:; script-src 'self' https://trusted.com;"
// Inline styles only
"default-src 'self'; style-src 'self' 'unsafe-inline';"
CSP Directives
CSP Directives
Script Sources
// Only same origin
script-src 'self'
// Specific CDN
script-src 'self' https://cdn.jsdelivr.net
// No inline scripts
script-src 'self'
// Allow specific inline
script-src 'self' 'nonce-abc123'
// Allow specific hash
script-src 'self' 'sha256-abc123...'
Style Sources
// Only external styles
style-src 'self'
// Allow inline styles
style-src 'self' 'unsafe-inline'
// Specific CDN
style-src 'self' https://fonts.googleapis.com
Image Sources
// Only same origin
img-src 'self'
// Allow data URIs
img-src 'self' data:
// Allow any HTTPS
img-src 'self' https:
// Specific domains
img-src 'self' https://images.example.com https://cdn.example.com
},
{
"id": "ch3",
"title": "Advanced CSP",
"content": "## Advanced CSP
Nonces and Hashes
// Generate nonce per request
const nonce = crypto.randomBytes(16).toString('base64');
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
`script-src 'self' 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}'`
);
res.locals.nonce = nonce;
next();
});
// In template
<script nonce="{nonce}">
// Inline script with nonce
</script>
// Hash-based
const hash = crypto
.createHash('sha256')
.update('console.log("hello")')
.digest('base64');
script-src 'self' 'sha256-${hash}'
Reporting
// CSP Report-Only header
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy-Report-Only',
"default-src 'self'; report-uri /csp-report"
);
next();
});
// Report endpoint
app.post('/csp-report', (req, res) => {
const report = req.body;
console.log('CSP Violation:', report);
res.status(204).end();
});
// Report-To header (newer)
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; report-to csp-endpoint"
);
res.setHeader(
'Report-To',
JSON.stringify({
group: 'csp-endpoint',
endpoints: [{ url: '/csp-report' }],
max_age: 10886400,
})
);
next();
});
React CSP Implementation
// server.js
import { randomBytes } from 'crypto';
function generateNonce() {
return randomBytes(16).toString('base64');
}
app.use((req, res, next) => {
const nonce = generateNonce();
res.locals.nonce = nonce;
const csp = [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}'`,
`style-src 'self' 'nonce-${nonce}'`,
"img-src 'self' data: https:",
"font-src 'self' https://fonts.gstatic.com",
"connect-src 'self' https://api.example.com",
"frame-ancestors 'none'",
].join('; ');
res.setHeader('Content-Security-Policy', csp);
next();
});
// React component
function App({ nonce }) {
return (
<html>
<head>
<script nonce={nonce} src="/bundle.js"></script>
<style nonce={nonce}>{css}</style>
</head>
<body>
<div id="root"></div>
</body>
</html>
);
}
Common CSP Mistakes
- Using 'unsafe-inline' (weakens CSP)
- Using 'unsafe-eval' (allows eval())
- Overly permissive policies (https: or *)
- Not testing in report-only mode first
- Missing directives (default-src fallback)
- Not handling nonce properly
Testing CSP
// Test CSP headers
describe('CSP', () => {
it('should set CSP header', async () => {
const response = await request(app).get('/');
expect(response.headers['content-security-policy']).toBeDefined();
});
it('should not allow unsafe-inline', async () => {
const response = await request(app).get('/');
expect(response.headers['content-security-policy'])
.not.toContain('unsafe-inline');
});
});
Quiz
1. What is CSP?
2. What does 'unsafe-inline' allow?
3. What is a common mistake when implementing Content Security Policy (CSP)?
Flashcards
Question
What is CSP?
Click to reveal answer
Answer
Content Security Policy - an HTTP header that restricts resource loading and execution.
Question
What is the default-src directive?
Click to reveal answer
Answer
Fallback for all resource types when specific directive is not set.
Question
Why avoid unsafe-inline?
Click to reveal answer
Answer
It allows inline scripts/styles, making XSS attacks easier.
Question
What is Report-Only mode?
Click to reveal answer
Answer
Reports CSP violations without blocking resources, useful for testing.
Revision Notes
Key Takeaways
- 1. CSP restricts which resources can be loaded
- 2. Use nonces instead of unsafe-inline
- 3. Start with Report-Only for testing
- 4. Be specific with allowed domains
- 5. CSP is a critical defense against XSS
Interview Tips
- • Explain CSP directives and source values
- • Discuss nonces vs hashes for inline scripts
- • Know common CSP configuration mistakes
Cheat Sheet
CSP Cheat Sheet
Directives
- default-src: Fallback for all
- script-src: JavaScript
- style-src: CSS
- img-src: Images
- connect-src: AJAX/WebSocket
Source Values
- 'self': Same origin
- 'none': Block all
- 'unsafe-inline': Inline scripts (avoid)
- 'nonce-abc': Allow specific inline
- https: HTTPS only
Best Practices
- Start with Report-Only
- Use nonces for inline
- Don't use unsafe-inline/eval
- Be specific with domains
- Set frame-ancestors 'none'
Header Example
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-abc';