Content Security Policy
Configure Content-Security-Policy headers to mitigate XSS, data injection, and clickjacking attacks.
You are an expert in configuring Content-Security-Policy (CSP) headers to restrict resource loading, mitigate XSS, and prevent data exfiltration in web applications. ## Key Points 1. **Start with Report-Only mode**: Deploy `Content-Security-Policy-Report-Only` first to collect violations without breaking the site. 2. **Use nonce-based script-src**: Nonces with `'strict-dynamic'` are the strongest practical CSP for modern applications. 3. **Set `object-src 'none'`**: Plugins are a major attack vector and are rarely needed. 4. **Set `frame-ancestors 'none'` or `'self'`**: This replaces `X-Frame-Options` and prevents clickjacking. 5. **Restrict `base-uri` and `form-action`**: These are often overlooked but can be exploited to redirect forms or rewrite relative URLs. 6. **Avoid `'unsafe-inline'` and `'unsafe-eval'`**: These negate much of CSP's protection. Use nonces or refactor inline code. 7. **Scope `connect-src` tightly**: Only allow the API origins your app actually calls. This limits data exfiltration if XSS occurs. 8. **Monitor violation reports**: Set up `report-uri` or `report-to` and monitor for unexpected violations — they may indicate injection attempts. 9. **Generate a new nonce per request**: Never reuse nonces. They must be cryptographically random and unique per response. - **Adding `'unsafe-inline'` to fix broken styles**: Instead, use nonces for inline styles or move styles to external files. - **Forgetting `'strict-dynamic'`**: Without it, scripts loaded dynamically by trusted scripts are blocked, breaking many frameworks. - **Setting CSP in a `<meta>` tag**: The `<meta>` tag does not support `frame-ancestors`, `report-uri`, or `sandbox`. Use the HTTP header.
skilldb get security-practices-skills/Content Security PolicyFull skill: 269 linesContent Security Policy — Application Security
You are an expert in configuring Content-Security-Policy (CSP) headers to restrict resource loading, mitigate XSS, and prevent data exfiltration in web applications.
Core Philosophy
Content-Security-Policy is fundamentally about shifting trust decisions from runtime to deploy time. Rather than hoping that no attacker-controlled content will ever reach the browser, CSP declares an explicit contract between the server and the browser about what resources are legitimate. Every resource that loads on the page must have been anticipated and approved by the developer who wrote the policy.
This philosophy extends beyond just blocking scripts. A well-crafted CSP encodes the application's architecture into a security boundary. It forces developers to think about where their resources come from, which third parties they depend on, and what the blast radius would be if any single origin were compromised. The discipline of writing a tight CSP often reveals unnecessary dependencies and architectural shortcuts that weaken the overall security posture.
The strongest CSP deployments treat the policy as living infrastructure, not a one-time configuration. They start in report-only mode, analyze real-world violations to tighten directives iteratively, and integrate CSP monitoring into their incident response pipeline. The goal is not a perfect policy on day one but a continuously improving contract that narrows the window of exploitability over time.
Anti-Patterns
-
Blanket
unsafe-inlineto "make it work": Adding'unsafe-inline'toscript-srcbecause inline scripts break during testing effectively disables CSP's primary protection against XSS. Instead, invest the time to adopt nonces or move scripts to external files. -
Copy-paste policies from tutorials without adaptation: Deploying a generic CSP from a blog post without auditing which origins your application actually uses leads to either an overly permissive policy that protects nothing or a broken site that blocks legitimate resources.
-
Treating CSP as a substitute for proper output encoding: CSP is a second line of defense, not a replacement for sanitizing and encoding output. An application that relies solely on CSP for XSS prevention will be vulnerable whenever the policy has gaps or is bypassed through trusted origins.
-
Allowing entire CDN domains in
script-src: Whitelisting a broad CDN origin likehttps://cdn.jsdelivr.netpermits any script hosted on that CDN to execute, including attacker-uploaded packages. Use specific paths, subresource integrity hashes, or nonce-based policies instead. -
Deploying enforcement mode without a report-only trial period: Skipping report-only mode and going straight to enforcement risks breaking production functionality for users. Always collect and analyze violation reports before enforcing a new or tightened policy.
Overview
Content-Security-Policy (CSP) is an HTTP response header that tells the browser which sources of content (scripts, styles, images, fonts, frames, etc.) are permitted on a page. It acts as a second line of defense against XSS — even if an attacker injects a script tag, the browser blocks it if the source is not in the CSP allowlist. CSP can also prevent clickjacking, mixed content, and data exfiltration.
Core Concepts
CSP Directives
| Directive | Controls |
|---|---|
default-src | Fallback for all resource types not explicitly set |
script-src | JavaScript sources |
style-src | CSS sources |
img-src | Image sources |
font-src | Font file sources |
connect-src | XHR, Fetch, WebSocket endpoints |
frame-src | Sources allowed in <iframe> |
frame-ancestors | Which origins can embed this page (replaces X-Frame-Options) |
object-src | Plugin sources (Flash, Java) — set to 'none' |
base-uri | Restricts <base> tag URLs |
form-action | Restricts form submission targets |
report-uri / report-to | Where to send violation reports |
Source Values
| Value | Meaning |
|---|---|
'self' | Same origin as the document |
'none' | Block all sources |
'unsafe-inline' | Allow inline scripts/styles (weakens CSP significantly) |
'unsafe-eval' | Allow eval() and similar dynamic code |
'nonce-<base64>' | Allow specific inline scripts with a matching nonce |
'strict-dynamic' | Trust scripts loaded by already-trusted scripts |
https: | Any HTTPS source |
data: | Data URIs |
blob: | Blob URIs |
| Specific origin | e.g., https://cdn.example.com |
Nonce-Based CSP
A nonce is a random value generated per-request. Inline scripts are only executed if they carry a matching nonce attribute. This is the recommended approach for allowing inline scripts without 'unsafe-inline'.
Report-Only Mode
Content-Security-Policy-Report-Only enforces nothing but sends violation reports. Use it to test a policy before deploying it in enforcement mode.
Implementation Patterns
Strict Nonce-Based CSP (Express)
const crypto = require('crypto');
function cspMiddleware(req, res, next) {
// Generate a unique nonce per request
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.cspNonce = nonce;
const policy = [
`default-src 'self'`,
`script-src 'nonce-${nonce}' 'strict-dynamic'`,
`style-src 'self' 'nonce-${nonce}'`,
`img-src 'self' https://cdn.example.com data:`,
`font-src 'self' https://fonts.gstatic.com`,
`connect-src 'self' https://api.example.com`,
`frame-ancestors 'none'`,
`object-src 'none'`,
`base-uri 'self'`,
`form-action 'self'`,
`report-uri /api/csp-report`,
].join('; ');
res.setHeader('Content-Security-Policy', policy);
next();
}
app.use(cspMiddleware);
<!-- In templates, use the nonce on inline scripts -->
<script nonce="<%= cspNonce %>">
// This inline script is allowed by the nonce
initApp();
</script>
<!-- Scripts without the correct nonce are blocked -->
Helmet.js CSP (Express)
const helmet = require('helmet');
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: [
"'self'",
(req, res) => `'nonce-${res.locals.cspNonce}'`,
"'strict-dynamic'",
],
styleSrc: [
"'self'",
(req, res) => `'nonce-${res.locals.cspNonce}'`,
],
imgSrc: ["'self'", "https://cdn.example.com", "data:"],
connectSrc: ["'self'", "https://api.example.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
},
reportOnly: false,
})
);
Django CSP with django-csp
# settings.py
MIDDLEWARE = [
'csp.middleware.CSPMiddleware',
# ...
]
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'",) # Nonces added automatically by django-csp
CSP_STYLE_SRC = ("'self'",)
CSP_IMG_SRC = ("'self'", "https://cdn.example.com", "data:")
CSP_FONT_SRC = ("'self'", "https://fonts.gstatic.com")
CSP_CONNECT_SRC = ("'self'", "https://api.example.com")
CSP_OBJECT_SRC = ("'none'",)
CSP_FRAME_ANCESTORS = ("'none'",)
CSP_BASE_URI = ("'self'",)
CSP_FORM_ACTION = ("'self'",)
CSP_INCLUDE_NONCE_IN = ['script-src', 'style-src']
CSP_REPORT_URI = '/api/csp-report'
# In templates:
# <script nonce="{{ request.csp_nonce }}">...</script>
Next.js CSP (Middleware)
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
const response = NextResponse.next();
const csp = [
`default-src 'self'`,
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
`style-src 'self' 'nonce-${nonce}'`,
`img-src 'self' blob: data:`,
`connect-src 'self'`,
`font-src 'self'`,
`object-src 'none'`,
`frame-ancestors 'none'`,
`base-uri 'self'`,
`form-action 'self'`,
].join('; ');
response.headers.set('Content-Security-Policy', csp);
response.headers.set('x-nonce', nonce);
return response;
}
CSP Violation Reporting Endpoint
app.post('/api/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
const report = req.body['csp-report'];
console.warn('CSP Violation:', {
blockedUri: report['blocked-uri'],
violatedDirective: report['violated-directive'],
documentUri: report['document-uri'],
sourceFile: report['source-file'],
lineNumber: report['line-number'],
});
// Forward to your logging/monitoring service
// Do not expose internal details in the response
res.status(204).end();
});
Nginx CSP Header
server {
listen 443 ssl;
server_name app.example.com;
# Static CSP (no nonce — works for apps with no inline scripts)
add_header Content-Security-Policy
"default-src 'self'; "
"script-src 'self' https://cdn.example.com; "
"style-src 'self' https://cdn.example.com; "
"img-src 'self' data:; "
"font-src 'self'; "
"connect-src 'self' https://api.example.com; "
"object-src 'none'; "
"frame-ancestors 'none'; "
"base-uri 'self'; "
"form-action 'self'"
always;
}
Best Practices
- Start with Report-Only mode: Deploy
Content-Security-Policy-Report-Onlyfirst to collect violations without breaking the site. - Use nonce-based script-src: Nonces with
'strict-dynamic'are the strongest practical CSP for modern applications. - Set
object-src 'none': Plugins are a major attack vector and are rarely needed. - Set
frame-ancestors 'none'or'self': This replacesX-Frame-Optionsand prevents clickjacking. - Restrict
base-uriandform-action: These are often overlooked but can be exploited to redirect forms or rewrite relative URLs. - Avoid
'unsafe-inline'and'unsafe-eval': These negate much of CSP's protection. Use nonces or refactor inline code. - Scope
connect-srctightly: Only allow the API origins your app actually calls. This limits data exfiltration if XSS occurs. - Monitor violation reports: Set up
report-uriorreport-toand monitor for unexpected violations — they may indicate injection attempts. - Generate a new nonce per request: Never reuse nonces. They must be cryptographically random and unique per response.
Common Pitfalls
- Adding
'unsafe-inline'to fix broken styles: Instead, use nonces for inline styles or move styles to external files. - Forgetting
'strict-dynamic': Without it, scripts loaded dynamically by trusted scripts are blocked, breaking many frameworks. - Setting CSP in a
<meta>tag: The<meta>tag does not supportframe-ancestors,report-uri, orsandbox. Use the HTTP header. - Overly permissive
default-src: Settingdefault-src *ordefault-src 'self' 'unsafe-inline' 'unsafe-eval'provides almost no protection. - Not testing with real browsers: CSP behavior varies across browsers. Test violations in Chrome, Firefox, and Safari.
- Allowing broad CDN origins:
script-src https://cdn.jsdelivr.netallows any package on that CDN. An attacker could host malicious JS there. Use specific paths or hashes when possible. - Ignoring violation reports: CSP reports reveal real attacks and policy misconfigurations. Set up alerting on abnormal report volumes.
Install this skill directly: skilldb add security-practices-skills
Related Skills
CORS Security
Configure CORS headers correctly to control cross-origin resource access while preventing overly permissive policies.
CSRF Protection
Protect web applications against cross-site request forgery (CSRF) using tokens, SameSite cookies, and origin validation.
Input Validation
Validate and sanitize all user input at application boundaries using schemas, type coercion, and allowlists.
Secrets Management
Securely store, access, rotate, and audit application secrets and credentials using vaults, environment variables, and CI/CD integrations.
SQL Injection
Prevent SQL injection attacks using parameterized queries, ORM best practices, and input validation layers.
Supply Chain Security
Secure your software supply chain by auditing dependencies, pinning versions, verifying integrity, and monitoring for vulnerabilities.