FreshScanSentry Free vulnerability scanning for your network & web apps Run a free scan

Medium risk Website security header

Missing Content-Security-Policy header: what it means and how to fix it

Short answer: a Content-Security-Policy (CSP) header tells browsers which places your website may load scripts and other content from. Without one, if an attacker manages to slip a malicious script into a page, through a form, a comment or a hacked plugin, the browser will run it for every visitor.

The fix: add a CSP header, starting in "report only" mode so nothing breaks while you tune it. A safe first policy:

Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'

What CSP protects against

The attack CSP is built for is cross-site scripting (XSS). An attacker gets their own JavaScript to run on your site. That script can steal logins and session cookies, change what customers see, add a fake payment form, or send visitors elsewhere.

CSP is a second layer of defense. Your site should still validate and escape anything users type in. But if a bug or a compromised plugin lets a script through, a good CSP stops the browser from loading it from an attacker's server or running it inline.

The frame-ancestors part of CSP also stops other sites from showing your pages inside an invisible frame to trick visitors into clicking things. This is called clickjacking.

How serious is it?

A missing CSP isn't a vulnerability by itself. It means one important safety net is missing. FreshScanSentry rates it Medium, as OWASP ZAP does, because it makes any XSS bug much more damaging. It matters most for sites with logins, checkouts, customer data or admin areas.

How it looks in your report shop.example.com
  1. 443 Content Security Policy (CSP) Header Not Set Found on 10 URLs Medium
  2. 443 Missing Anti-clickjacking Header Fixed by frame-ancestors Medium
Found by the Deep Scan's web app checks

How to check your site

curl -sI https://your-site.com | grep -i content-security-policy

No output means there's no CSP header. Some sites set CSP in a <meta http-equiv="Content-Security-Policy"> tag instead. That works for most rules, but not frame-ancestors or reporting, so a header is better.

How to add a CSP without breaking your site

1. List what your site loads from elsewhere

Open your site with the browser's developer tools on the Network tab and note every outside domain: analytics, tag managers, fonts, chat widgets, payment forms, embedded videos, maps. Each one needs to be allowed in the right directive:

DirectiveControlsExample
default-srcFallback for everything not listed'self'
script-srcJavaScript'self' https://www.googletagmanager.com
style-srcStylesheets'self' https://fonts.googleapis.com
font-srcWeb fonts'self' https://fonts.gstatic.com
img-srcImages'self' data: https:
connect-srcBackground requests (APIs, analytics)'self' https://*.google-analytics.com
frame-srcThings you embed (videos, payment forms)https://www.youtube.com https://js.stripe.com
frame-ancestorsWho may embed your pages'self' or 'none'

2. Start in report-only mode

Send your policy as Content-Security-Policy-Report-Only first. Browsers then report what the policy would block, in the developer tools console, without blocking anything. Browse every important page, including checkout, login, contact forms and the admin area, and add any missing sources.

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' https://www.googletagmanager.com; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'

3. Add the header to your server

Use Content-Security-Policy-Report-Only while testing, then switch the name to Content-Security-Policy to enforce it.

Apache (needs mod_headers):

Header always set Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'"

nginx (in the server block):

add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'" always;

IIS. In IIS Manager, open HTTP Response Headers for the site, choose Add, and enter Content-Security-Policy as the name and your policy as the value. Or add it under <customHeaders> in web.config.

Cloudflare. Go to Rules → Transform Rules → Modify Response Header, create a rule for your site, and Set static a header named Content-Security-Policy with your policy.

Node.js / Express with helmet:

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    objectSrc: ["'none'"],
    baseUri: ["'self'"],
    formAction: ["'self'"],
    frameAncestors: ["'self'"],
  },
}));

4. Enforce it, then tighten over time

When report-only mode shows nothing important being blocked, switch to the enforcing header and check the site again. Then work toward removing 'unsafe-inline' and 'unsafe-eval' from script-src if you had to add them. Those two are what let injected scripts run. Modern sites replace them with a per-page nonce or hashes for the inline scripts they really need.

For comparison, this site's own policy is default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none' and a few more rules. Scripts only load from our own domain. Inline styles are allowed because they can't run code.

If the scan says your CSP is weak

OWASP ZAP also flags policies that exist but leave gaps. Common ones:

Google's CSP Evaluator checks a policy and explains each weakness.

While you're there: two more headers

Scanners usually flag these alongside CSP. Each is a single line.

# Apache
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"

# nginx
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;

Common questions

We use WordPress with lots of plugins. Is CSP realistic?

Yes, if you start small. object-src 'none'; base-uri 'self'; frame-ancestors 'self' almost never breaks anything and already fixes the clickjacking finding. Plugins often add inline scripts, so a strict script-src takes more testing in report-only mode. Some security plugins can manage CSP for you.

Why do I see "Refused to load…" errors after adding CSP?

The policy is blocking something your page uses. The error names the blocked address and the directive. Add that domain to the directive if you trust it, or remove the thing loading it.

Does CSP replace fixing XSS bugs?

No. It limits the damage if one gets through. Keep plugins, themes and frameworks updated, and escape user input.

Sources: MDN: Content Security Policy · W3C CSP Level 3 · OWASP CSP cheat sheet · ZAP alert: CSP Header Not Set · Google CSP Evaluator