Content-Security-Policy missing

The page is served without an enforced Content-Security-Policy. If an attacker finds any way to inject markup, the browser will run whatever script they add.

Do this: Add Content-Security-Policy starting from default-src 'self'. With no CSP, any injected script runs with full access to the page, its cookies and its forms.
PassAn enforced Content-Security-Policy is present.
MediumNo enforced Content-Security-Policy header was found.

The fix, in one snippet

Example to adapt Start strict, then loosen deliberately
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'

Illustrative values. Change the paths, hostnames and options to match your own site before using it.

The sections below explain what is tested, why it matters and the alternatives.

What we test

Scan.now reads the Content-Security-Policy header from the HTTPS response to /. If there is no header, it also looks for a <meta http-equiv="Content-Security-Policy"> element in the HTML, which browsers honour for most directives (but not frame-ancestors, report-uri or sandbox). The check passes when an enforced policy exists that constrains script execution through script-src or a default-src fallback. It fails when there is no policy at all, and it also fails, with an explanatory note, when only Content-Security-Policy-Report-Only is present, because a report-only policy blocks nothing. The quality of the directives (for example 'unsafe-inline' or wildcards) is assessed separately in csp-weak-directives, and clickjacking protection via frame-ancestors in x-frame-options.

Why it matters

Cross-site scripting remains one of the most common web vulnerabilities (OWASP Top 10:2021 folds it into A03 Injection). Output encoding is the primary defence, but a single missed sink in a template, a third-party widget or an old library is enough. A Content Security Policy is the second layer: even when an attacker manages to inject <script>, the browser refuses to execute anything that is not from an allowed source or carrying the right nonce. A realistic scenario is a comment field that escapes most characters but not an attribute context; without CSP the injected handler steals session tokens or silently rewrites a payment form (the Magecart pattern). CSP also governs where forms may post, which origins may frame the page, and whether plugins load. Our CSP guide and XSS guide go deeper.

How to fix it

Start in report-only mode, watch the reports for a few days, then enforce. A nonce-based "strict" policy is the modern recommendation because it does not depend on maintaining a host allowlist:

Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self'; upgrade-insecure-requests

The nonce must be fresh per response, so it is generated by the application rather than the web server. Express with Helmet:

app.use((req, res, next) => { res.locals.nonce = crypto.randomBytes(16).toString('base64'); next(); });
app.use(helmet.contentSecurityPolicy({
  directives: {
    scriptSrc: [(req, res) => `'nonce-${res.locals.nonce}'`, "'strict-dynamic'"],
    objectSrc: ["'none'"], baseUri: ["'none'"], frameAncestors: ["'self'"],
  },
}));

Static sites without inline scripts can use a simple allowlist set at the server. nginx:

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

Apache: Header always set Content-Security-Policy "...". On Cloudflare, a Transform Rule (Rules > Transform Rules > Modify Response Header) adds the header at the edge. For rollout, send the same policy as Content-Security-Policy-Report-Only with a report-to directive and a Reporting-Endpoints header, fix what the reports show, then switch header names.

Where this fits

Content-Security-Policy missing is check 4 of 13 that the website vulnerability scanner runs under http security headers, ordered the way they are worth fixing. That ordering is the point: Fixing this one while the check above it still fails buys less than it looks like.

Fix this one first

Above it in the same category sits Content-Security-Policy is weak (medium), where a Content-Security-Policy exists but contains directives that let injected script run anyway: 'unsafe-inline' without nonces, 'unsafe-eval', wildcards, data: URIs, or missing object-src and base-uri. An attacker who has that does not need this, so it is the better use of the same hour.

What fixing this still leaves open

Content-Security-Policy missing closes one route in. Immediately below it: Cross-Origin-Opener-Policy, where the page does not set Cross-Origin-Opener-Policy, so windows it opens, or that open it, from other origins keep a reference to it and share its browsing context group; Permissions-Policy, where no Permissions-Policy header restricts powerful browser features; Referrer-Policy, where no Referrer-Policy is set, or it is set to a value that sends full URLs to other sites.

Found in the same scan

The website vulnerability scanner reports this alongside checks from other categories that are at least as serious, including Certificate chain and hostname validation, where the certificate presented for this hostname did not validate: The chain does not reach a trusted root, an intermediate is missing, the name does not match, or the certificate is self-signed or expired, and Exposed .env configuration file, where a .env configuration file is served from the web root. A single run of website vulnerability scanner answers all of them at once.

Prompt for an AI Hand this check to an assistant Sign in to copy it
The first few lines
You are a senior web engineer. I ran a security and SEO scanner against my site and it reported the finding below. Fix it properly rather than suppressing the symptom.

Finding: Content-Security-Policy missing (medium severity)
Scanner check id: content-security-policy
17 more lines, including the evidence and the exact fix

The rest of this prompt names the pages and line numbers we found the problem on, the configuration to change, and the constraints a good answer has to respect. It is free, it just needs an account so the work is not scraped wholesale.

Sign in with Google

Signing in is free and takes one click. We store your email address and nothing else.

References

  1. MDN: Content-Security-Policy
  2. OWASP Content Security Policy Cheat Sheet
  3. web.dev: Mitigate XSS with a strict CSP
  4. W3C: Content Security Policy Level 3

Related guides