How to Fix Your Content Security Policy (CSP)
Content Security Policy is the most powerful — and most commonly misconfigured — HTTP security header. If your domain security scan has flagged a missing CSP or one containing unsafe-inline, this guide explains what the problem is and how to fix it properly. Unlike most headers, CSP cannot be fixed by copying a single value. The right policy depends on what your site actually loads. This guide walks you through building one correctly.
What this finding means
A Content Security Policy (CSP) is an HTTP response header that tells the browser exactly which sources of scripts, styles, images, fonts, and frames are allowed to load on your page. Anything not on the approved list is blocked — preventing cross-site scripting (XSS) attacks where injected malicious scripts execute in a visitor's browser.
mydomainrisk.com reports two CSP findings:
- No CSP at all — Your site has no Content-Security-Policy header. The browser has no restrictions on what scripts or resources can execute on your pages.
- CSP contains
unsafe-inline— Your policy exists but includes'unsafe-inline'in thescript-srcorstyle-srcdirective. This tells the browser to allow any inline script or style on the page — which means an attacker who can inject a single<script>tag into your HTML can execute arbitrary JavaScript. This largely defeats the purpose of having a CSP.
Why it matters
XSS is consistently one of the most exploited web vulnerabilities. A properly configured CSP is the single most effective browser-side defence — it limits what an attacker can do even if they manage to inject code into your page. Without it, a successful XSS attack can steal session cookies, redirect users, exfiltrate data, or deface your site.
unsafe-inline is the single most common CSP mistake. Many developers add it to stop their inline scripts from breaking rather than fixing the underlying code — but it removes the most important protection CSP provides.
Part 1 — Understanding why you can't just copy a CSP value
Unlike HSTS or X-Content-Type-Options, there is no universal correct CSP value. A policy that works perfectly for a static HTML site will break a React app. A policy that works for one CMS will block resources on another.
If you copy a CSP from a guide or another site and apply it to yours, one of two things will happen:
- It's too strict and breaks your site (images don't load, scripts fail, fonts disappear)
- It's too permissive to provide meaningful protection
The right approach is to observe first, then enforce — using CSP's built-in report-only mode.
Part 2 — Build your CSP using report-only mode
Step 1: Deploy a report-only policy first
Instead of Content-Security-Policy, use Content-Security-Policy-Report-Only. This header instructs the browser to evaluate your policy and report violations — but not block anything. Your site continues to work normally while you gather data.
Start with a deliberately strict policy in report-only mode:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'none'; report-uri /csp-report
Step 2: Set up a CSP report endpoint (optional but recommended)
The report-uri directive sends violation reports as JSON POST requests to an endpoint you specify. You can use a free service like report-uri.com to collect and visualise these reports without building your own endpoint. Alternatively, open DevTools → Console and look for CSP violation errors — they tell you exactly which resource was blocked and from which source.
Step 3: Browse your site thoroughly
With the report-only policy deployed, navigate through every page of your site — including authenticated areas, checkout flows, embedded maps, chat widgets, video players and any third-party integrations. Every blocked resource will generate a violation report.
Step 4: Interpret the violations
Each violation tells you which directive was violated (e.g. script-src) and which source was blocked (e.g. https://cdn.jsdelivr.net). For each legitimate source, add it to the appropriate directive:
script-src 'self' https://cdn.jsdelivr.net;
Step 5: Build your enforced policy
Once violations have stopped appearing for legitimate resources, your policy is ready to enforce. Switch from Content-Security-Policy-Report-Only to Content-Security-Policy.
Part 3 — Removing unsafe-inline
If your site currently uses unsafe-inline and you want to remove it, the challenge is that inline scripts and styles need an alternative mechanism. There are two options:
Option A: Nonces (recommended for dynamic sites)
A nonce is a random, single-use token generated per page request. You add it to both the CSP header and each inline script tag.
In your CSP header:
Content-Security-Policy: script-src 'self' 'nonce-RANDOM_BASE64_VALUE'
On each inline script tag:
<script nonce="RANDOM_BASE64_VALUE"> // your inline script </script>
The nonce must be cryptographically random (at least 128 bits), different on every page load, and never reused. Most server-side frameworks (Next.js, Django, Laravel, Rails) have built-in or easy nonce support.
Option B: Hashes (recommended for static sites)
For inline scripts that never change, you can whitelist them by their SHA-256 hash instead of using a nonce:
Content-Security-Policy: script-src 'self' 'sha256-BASE64_HASH_OF_SCRIPT_CONTENT'
Generate the hash using:
echo -n 'your inline script content here' | openssl dgst -sha256 -binary | openssl base64
Part 4 — Common CSP directives reference
| Directive | Controls |
|---|---|
default-src | Fallback for all resource types not explicitly listed |
script-src | JavaScript sources |
style-src | CSS sources |
img-src | Image sources |
font-src | Web font sources |
connect-src | Fetch, XHR, WebSocket connections |
frame-src | Iframe sources |
object-src | Plugin content (Flash etc.) — set to 'none' |
base-uri | Restricts <base> tag — set to 'self' |
form-action | Where forms can submit to |
Common safe values: 'self' — same origin only · 'none' — block entirely · https: — any HTTPS source · data: — data URIs (commonly needed for img-src)
Part 5 — Deploy the header by platform
Cloudflare (Transform Rules)
Security → Transform Rules → Modify Response Header → Add rule → Set Content-Security-Policy to your policy value.
Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self'" always;
Apache
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self'"
Vercel (vercel.json)
{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self'"
}
]
}
]
}Netlify (netlify.toml)
[[headers]]
for = "/*"
[headers.values]
Content-Security-Policy = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self'"The example values above include 'unsafe-inline' in style-src because many sites load inline styles through CSS frameworks. Remove it if your site doesn't use inline styles and replace with nonces or hashes.
Verify it worked
Scan your domain at mydomainrisk.com — the CSP finding will update to show whether your policy is present and whether unsafe-inline has been removed from script-src. The scan also detects whether your CSP is in enforce mode or report-only mode and flags the distinction. If you're using report-only mode deliberately while you refine your policy, that's fine — the finding will note this rather than treating it as a failure.
Still seeing unsafe-inline after deploying your fix?
Check that:
- Your header is being served on the correct response (check DevTools → Network → click your page → Response Headers)
- You don't have a conflicting CSP set elsewhere (e.g. a CDN adding its own header)
- Your nonces are actually being injected into your inline script tags at render time
Check your Content Security Policy
MyDomainRisk detects whether your CSP is present, enforced, and free of unsafe-inline — and scores it as part of your overall security grade.
Scan your domain free →