strict-dynamic to prevent inline script injection (XSS) -- start in report-only mode, then enforce after validating no breakage.Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none';'unsafe-inline' in script-src defeats CSP's entire XSS protection -- use nonces or hashes instead.strict-dynamic) supported in Chrome 59+, Firefox 58+, Safari 15.4+, Edge 79+.Content-Security-Policy-Report-Only before enforcing -- deploying CSP in enforcement mode without testing WILL break site functionality'unsafe-inline' for script-src in production -- it defeats CSP's primary XSS protection*) as a source for script-src or default-src -- it allows loading scripts from any originframe-ancestors directive CANNOT be set via <meta> tag -- it must be sent as an HTTP header| # | Directive | Category | Controls | Default Fallback |
|---|---|---|---|---|
| 1 | default-src | Fetch | Fallback for all other fetch directives | None (browser default) |
| 2 | script-src | Fetch | JavaScript and WebAssembly resources | default-src |
| 3 | style-src | Fetch | CSS stylesheets | default-src |
| 4 | img-src | Fetch | Images and favicons | default-src |
| 5 | connect-src | Fetch | XHR, Fetch, WebSocket, EventSource | default-src |
| 6 | font-src | Fetch | Web fonts via @font-face | default-src |
| 7 | frame-src | Fetch | <iframe> and <frame> sources | child-src then default-src |
| 8 | media-src | Fetch | <audio>, <video>, <track> elements | default-src |
| 9 | object-src | Fetch | <object>, <embed> plugins | default-src |
| 10 | worker-src | Fetch | Worker, SharedWorker, ServiceWorker | child-src then script-src then default-src |
| 11 | base-uri | Document | URLs for <base> element | No fallback (allows any) |
| 12 | form-action | Navigation | Form submission target URLs | No fallback (allows any) |
| 13 | frame-ancestors | Navigation | Who can embed this page (replaces X-Frame-Options) | No fallback (allows any) |
| 14 | upgrade-insecure-requests | Document | Auto-upgrade HTTP to HTTPS | Disabled |
| 15 | report-to | Reporting | Where to send violation reports (replaces report-uri) | No reporting |
| Keyword | Meaning | Use In |
|---|---|---|
'self' | Same origin only | Any directive |
'none' | Block all resources of this type | Any directive |
'unsafe-inline' | Allow inline <script> and <style> (dangerous) | script-src, style-src |
'unsafe-eval' | Allow eval(), new Function(), setTimeout(string) | script-src |
'nonce-{random}' | Allow elements with matching nonce attribute | script-src, style-src |
'sha256-{hash}' | Allow elements matching the hash | script-src, style-src |
'strict-dynamic' | Trust scripts loaded by already-trusted scripts | script-src |
'unsafe-hashes' | Allow inline event handlers matching hashes | script-src |
https: | Allow any HTTPS origin | Any directive |
data: | Allow data: URIs | Any directive |
START: What is your CSP deployment scenario?
├── New application (no existing inline scripts)?
│ ├── YES → Deploy strict nonce-based CSP immediately (see Step 1)
│ └── NO ↓
├── Existing app with inline scripts you control?
│ ├── YES → Add nonces to all inline scripts, then deploy strict CSP (see Step 2)
│ └── NO ↓
├── Existing app with third-party inline scripts?
│ ├── YES → Use hash-based CSP for static scripts + nonces for dynamic (see Step 3)
│ └── NO ↓
├── Legacy app that cannot be modified?
│ ├── YES → Deploy allowlist-based CSP as interim measure, plan migration to strict CSP
│ └── NO ↓
├── Need to prevent clickjacking only?
│ ├── YES → Use frame-ancestors 'none' or 'self' (Step 5)
│ └── NO ↓
└── DEFAULT → Start with report-only mode to audit current resource loading (Step 1)
Start by deploying a strict policy in report-only mode to discover what would break without actually blocking anything. [src6]
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self';
object-src 'none';
base-uri 'none';
report-to csp-endpoint;
Verify: Open browser DevTools > Console -- CSP violations appear as warnings with [Report Only] prefix.
Generate a cryptographically random nonce per request and add it to every inline <script> tag. [src5]
Content-Security-Policy: script-src 'nonce-dGhpcyBpcyBhIG5vbmNl' 'strict-dynamic'
Verify: Inline scripts without a nonce should be blocked. Check console for Refused to execute inline script errors.
Combine the essential directives for a production-ready strict CSP. [src3]
Content-Security-Policy:
default-src 'self';
script-src 'nonce-{RANDOM}' 'strict-dynamic';
style-src 'self' 'nonce-{RANDOM}';
img-src 'self' https:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-src 'none';
object-src 'none';
base-uri 'none';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
report-to csp-endpoint;
Verify: Run your CSP through Google's CSP Evaluator at https://csp-evaluator.withgoogle.com/.
After monitoring report-only mode for at least 1-2 weeks with no unexpected violations, switch to enforcement. [src6]
curl -sI https://your-site.com | grep -i content-security-policy
Verify: Should return the enforced policy (not report-only).
Replace the legacy X-Frame-Options header with CSP frame-ancestors. [src1]
# Prevent all framing
Content-Security-Policy: frame-ancestors 'none';
# Allow same-origin framing only
Content-Security-Policy: frame-ancestors 'self';
# Allow specific origins
Content-Security-Policy: frame-ancestors 'self' https://trusted-partner.com;
Verify: Try embedding your page in an <iframe> on a different origin -- it should be blocked.
const express = require('express'); // ^4.18.0
const helmet = require('helmet'); // ^8.0.0
const crypto = require('crypto');
const app = express();
// Generate per-request nonce middleware
app.use((req, res, next) => {
res.locals.cspNonce = crypto.randomBytes(16).toString('base64');
next();
});
// Configure CSP via Helmet
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: [
(req, res) => `'nonce-${res.locals.cspNonce}'`,
"'strict-dynamic'"
],
styleSrc: ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`],
imgSrc: ["'self'", "https:"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
baseUri: ["'none'"],
formAction: ["'self'"],
frameAncestors: ["'none'"],
upgradeInsecureRequests: []
}
}
}));
// In templates: <script nonce="<%= cspNonce %>">...</script>
# settings.py -- using django-csp >= 4.0
# pip install django-csp
MIDDLEWARE = ["csp.middleware.CSPMiddleware", ...]
CONTENT_SECURITY_POLICY = {
"DIRECTIVES": {
"default-src": ["'self'"],
"script-src": ["'strict-dynamic'"], # nonces added automatically
"style-src": ["'self'"],
"img-src": ["'self'", "https:"],
"connect-src": ["'self'"],
"font-src": ["'self'"],
"object-src": ["'none'"],
"base-uri": ["'none'"],
"form-action": ["'self'"],
"frame-ancestors": ["'none'"],
"upgrade-insecure-requests": True,
}
}
# In templates: {% load csp %} <script nonce="{% csp_nonce %}">...</script>
# /etc/nginx/snippets/csp-headers.conf
# Hash-based CSP for static sites (Nginx cannot generate nonces natively)
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' 'sha256-{HASH_OF_INLINE_SCRIPT}';
style-src 'self';
img-src 'self' https:;
font-src 'self';
connect-src 'self';
object-src 'none';
base-uri 'none';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
" always;
# .htaccess or httpd.conf
Header always set Content-Security-Policy "\
default-src 'self'; \
script-src 'self' 'sha256-{HASH_OF_INLINE_SCRIPT}'; \
style-src 'self'; \
img-src 'self' https:; \
font-src 'self'; \
connect-src 'self'; \
object-src 'none'; \
base-uri 'none'; \
form-action 'self'; \
frame-ancestors 'none'; \
upgrade-insecure-requests"
# BAD -- unsafe-inline completely negates CSP's XSS protection
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
# Any XSS payload can execute inline scripts freely
# GOOD -- only scripts with the correct nonce execute
Content-Security-Policy: script-src 'nonce-abc123def456' 'strict-dynamic'
# XSS payloads cannot guess the per-request nonce
# BAD -- allows scripts from ANY origin
Content-Security-Policy: default-src *; script-src *
# GOOD -- use nonce-based CSP with strict-dynamic
Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none'
# BAD -- default-src https: allows loading from ANY HTTPS origin
Content-Security-Policy: default-src https:
# Attackers can host payloads on any HTTPS domain they control
# GOOD -- lock down default, open specific directives as needed
Content-Security-Policy: default-src 'none'; script-src 'nonce-{RANDOM}' 'strict-dynamic'; style-src 'self'; img-src 'self' https:; font-src 'self'; connect-src 'self'
<!-- BAD -- frame-ancestors, report-uri, report-to, and sandbox
are IGNORED in meta tags -->
<meta http-equiv="Content-Security-Policy"
content="frame-ancestors 'none'; report-uri /csp-report">
# GOOD -- all directives work via HTTP header
Content-Security-Policy: frame-ancestors 'none'; report-to csp-endpoint
# BAD -- allows eval(), new Function(), setTimeout(string)
Content-Security-Policy: script-src 'self' 'unsafe-eval'
// GOOD -- replace eval() with safe alternatives
// Instead of: setTimeout('doSomething()', 1000)
setTimeout(doSomething, 1000); // pass function reference
// Instead of: new Function('return ' + userInput)
JSON.parse(userInput); // for data parsing
Content-Security-Policy-Report-Only first and monitor for 1-2 weeks. [src6]crypto.randomBytes(16). [src5]object-src allows plugin injection; omitting base-uri allows <base> tag hijacking. Fix: Always include object-src 'none'; base-uri 'none'. [src3]strict-dynamic instead of domain allowlists. [src5]<meta http-equiv="Content-Security-Policy"> ignores frame-ancestors, report-uri, report-to, and sandbox. Fix: Set CSP via HTTP response headers. [src2]ws://) connections. Fix: Explicitly use wss:// for WebSocket connections. [src1]worker-src or script-src without accounting for service worker scope blocks SW registration. Fix: Include worker-src 'self' if you use service workers. [src2]report-uri is deprecated in CSP Level 3 in favor of report-to. Fix: Include both during the transition period. [src4]# Check current CSP headers of a live site
curl -sI https://your-site.com | grep -i content-security-policy
# Check CSP with full headers
curl -sI https://your-site.com | grep -iE '(content-security|report)'
# Generate SHA-256 hash of an inline script for hash-based CSP
echo -n 'console.log("hello")' | openssl dgst -sha256 -binary | openssl base64
# Validate CSP: visit https://csp-evaluator.withgoogle.com/
# Scan for inline scripts that need nonces
grep -rn '<script>' --include="*.html" --include="*.ejs" .
# Scan for inline event handlers that CSP will block
grep -rn 'onclick=\|onload=\|onerror=\|onsubmit=' --include="*.html" .
| CSP Level | Status | Browser Support | Key Features |
|---|---|---|---|
| CSP Level 3 | W3C Working Draft (June 2025) | Chrome 59+, Firefox 58+, Safari 15.4+, Edge 79+ | strict-dynamic, report-to, worker-src, manifest-src, nonce/hash enhancements |
| CSP Level 2 | W3C Recommendation | Chrome 40+, Firefox 31+, Safari 10+, Edge 15+ | script-src, style-src, base-uri, form-action, frame-ancestors, report-uri, nonce/hash |
| CSP Level 1 | W3C Recommendation (deprecated) | Chrome 25+, Firefox 23+, Safari 7+, Edge 12+ | Basic default-src, script-src, img-src, style-src, connect-src |
| Trusted Types | W3C Draft | Chrome 83+, Firefox behind flag | require-trusted-types-for 'script', DOM sink protection |
| Reporting API v1 | W3C Draft | Chrome 96+, Edge 96+ | report-to with Reporting-Endpoints header |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Any web application serving HTML to browsers | Static file server with no HTML (pure API) | CORS headers for API protection |
| Need XSS defense-in-depth beyond output encoding | CSP is your only XSS defense | Output encoding + CSP together |
| Controlling third-party script loading (analytics, ads) | You trust all content from all origins | Still use CSP -- trust boundaries change |
| Preventing clickjacking (frame-ancestors) | Only need clickjacking protection | X-Frame-Options as legacy fallback, but prefer CSP |
| Compliance requires CSP (PCI DSS, SOC 2) | Internal-only tool with no external access | CSP still recommended but lower priority |
strict-dynamic trusts scripts loaded by nonce-approved scripts -- if a trusted script has a gadget vulnerability (e.g., JSONP endpoint, prototype pollution), it can be exploited to bypass CSP<meta> tag does not support frame-ancestors, report-uri, report-to, or sandbox -- use HTTP headers for full protectionContent-Security-Policy -- only the non-standard X-Content-Security-Policy (IE 10-11, limited support)upgrade-insecure-requests does not upgrade WebSocket connections (ws:// to wss://) -- handle this explicitly in application code