I keep a copy of this page bookmarked because I can never remember the exact syntax for some of these directives. CSP has a lot of them, and most documentation either glosses over the details or buries them in paragraphs of text.
Here’s every directive you’ll actually use, with examples and the gotchas that trip people up.
Fetch Directives
These control what the browser is allowed to load. They’re the core of any CSP policy.
default-src
This is your fallback. When a specific directive isn’t set, the browser falls back to default-src. Think of it as the “catch-all.”
Setting this to ‘self’ means: “If I didn’t specifically say otherwise, only load from my own domain.”
The thing that catches people off guard: default-src does NOT apply to script-src and style-src in the way you might expect. If you set default-src 'self' but don’t set script-src, scripts will use default-src. But if you set default-src 'self' AND script-src 'unsafe-inline', then scripts use your script-src value, not default-src.
My recommendation: Always explicitly set script-src and style-src even if they match default-src. It makes your policy clearer and prevents surprises.
script-src
The most important directive. This is what stops XSS. It controls where JavaScript can be loaded from.
Source values you can use:
'self'— Your own domain only'nonce-<base64-value>'— Only scripts with this exact nonce attribute run'sha256-<hash>'— Only scripts whose content matches this hash run'strict-dynamic'— If a trusted script loads another script, trust that too'unsafe-inline'— Allow inline scripts (defeats XSS protection, avoid this)'unsafe-eval'— Allow eval(), new Function(), setTimeout(string) (dangerous, avoid this)https://cdn.example.com— Allow scripts from a specific domain
Here’s the thing about 'unsafe-inline' and 'unsafe-eval': using either of these in script-src essentially nullifies the XSS protection that CSP provides. If an attacker can inject a script tag, and you’ve allowed unsafe-inline, that injected script will execute. Period.
If you absolutely must use unsafe-inline (legacy app, can’t refactor), use it but understand the trade-off. You’re still getting value from other directives like frame-ancestors and connect-src.
style-src
Controls CSS stylesheets. Same source values as script-src.
Here’s where I diverge from the “be strict about everything” crowd: 'unsafe-inline' in style-src is acceptable in most cases. CSS can’t execute JavaScript. It can’t steal cookies. The worst a CSS injection can do is visually mess up your page or extract some content through clever background-image tricks. It’s a risk, but a much smaller one.
If you want to be thorough, use hashes for your critical inline styles and keep unsafe-inline as a fallback. Or use nonces.
img-src
Controls image sources.
The data: is important. A lot of sites use data URIs for inline SVGs, base64-encoded images, or transparent tracking pixels. Without data:, all of those break.
I generally use https: for img-src because images can’t execute code. Being overly restrictive with image sources causes more problems than it solves.
font-src
Controls web fonts.
You’ll need this if you use Google Fonts, Typekit, or any external font service. Easy to forget and a common source of CSP violations.
connect-src
Controls fetch(), XMLHttpRequest, WebSocket, EventSource, and navigator.sendBeacon().
This one matters more than people think. An attacker who can inject JavaScript but can’t execute it (because of script-src) might try to redirect the user or exfiltrate data through other means. connect-src limits where data can be sent.
Lock this down to your actual API endpoints and analytics domains. Don’t use wildcards.
media-src
Controls audio and video elements.
Most sites don’t need this if default-src covers it, but if you’re hosting video or audio content on a CDN, add the CDN domain here.
object-src
Controls plugin content: Flash, Java applets, etc.
Just set it to 'none'. Flash is dead, Java applets are dead, and no modern site needs plugins. This is free security.
frame-src
Controls what can be embedded in iframes on your page.
Note: if frame-src is not set, the browser falls back to child-src, and if that’s not set either, it falls back to default-src. Be explicit.
Document Directives
These don’t control resource loading — they control document-level behavior.
base-uri
Restricts the URL that can be used in the <base> element.
Without this, an attacker could inject <base href="https://evil.com/"> and redirect all relative URLs on your page to their server. It’s a subtle attack that most people don’t think about.
form-action
Restricts where forms can submit to.
This stops phishing attacks where an attacker injects a form that submits user credentials to their server. Simple but effective.
frame-ancestors
Controls who can embed YOUR page in an iframe. This is the inverse of frame-src.
This replaces the older X-Frame-Options header. Use this instead. If you set frame-ancestors ’none’, your page cannot be embedded anywhere, which prevents clickjacking attacks entirely.
navigate-to
Restricts navigation targets. This is relatively new and not supported everywhere yet.
Reporting Directives
report-uri
Where to send violation reports. Note: this is technically deprecated in favor of report-to, but browser support is still better for report-uri.
report-to
The modern reporting API.
Then in your CSP: report-to csp
Use both for maximum compatibility.
Utility Directives
upgrade-insecure-requests
Automatically upgrades HTTP requests to HTTPS. No exceptions, no mixed content.
block-all-mixed-content
Blocks all mixed content (HTTPS page loading HTTP resources). More aggressive than upgrade-insecure-requests because it blocks rather than upgrades.
Source Values Quick Reference
| Value | Meaning |
|---|---|
'self' |
Same origin only |
'none' |
Block everything |
'unsafe-inline' |
Allow inline code (risky) |
'unsafe-eval' |
Allow eval() (very risky) |
'nonce-abc' |
Must match nonce attribute |
'sha256-hash' |
Must match content hash |
'strict-dynamic' |
Trust scripts loaded by trusted scripts |
https: |
Any HTTPS URL |
http: |
Any HTTP URL |
data: |
data: URIs |
blob: |
blob: URIs |
*.example.com |
Any subdomain |
example.com |
Exact domain |