dotvitals

No Content Security Policy is enforced

HighConfirmedweb.security-headers.csp-missing

What this check looks for

Nothing limits where the page may load scripts from. If a script is ever injected — through a comment field, a compromised plugin, or a third-party widget — the browser will run it.

Why it matters

A Content Security Policy is the difference between a cross-site scripting bug being a defect and being a breach. It is the one control that limits the damage of code you did not write ending up on your page.

When the check passes, your report says: “A Content Security Policy limits where the page may load code from”.

What it costs your score

When this check fails it removes 22 points from your Web security score, before the status, confidence and repeat multipliers are applied. Web security carries a weight of 8 in the overall score.

It shares the web-security.csp family ceiling of 30 points: however many findings that family produces, together they cannot remove more than that from Web security. One underlying problem showing up in several places is still one problem.

Severity
high
Default confidence
confirmed
Status when triggered
fail
Deduction
22 points
Family cap
web-security.csp · 30
Category
Web security
Module
Web security headers
Fix owned by
user
In the ruleset since
2026.09

How the whole score is calculated

How to fix it

Deploy a policy that restricts where scripts may come from.

Without one, any injected script runs with the full authority of your page.

  1. Start in report-only mode with a report-uri or report-to so you can see what would break.

  2. Collect reports for a week and fix the legitimate sources they reveal.

  3. Prefer a per-request nonce with 'strict-dynamic' over a host allow-list — allow-lists are bypassable through any JSONP endpoint or hosted library on them.

  4. Switch the header from Content-Security-Policy-Report-Only to Content-Security-Policy.

How to confirm it worked

  • curl -sSI https://‹host›/ | grep -i '^content-security-policy:' — expect an enforcing header, not report-only

The configuration to publish
Content-Security-Policy: default-src 'self'; script-src 'nonce-{{RANDOM}}' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'

A named slot like ‹domain› — and the braces left in the configuration below — is filled in with your own values when this rule appears on a report.

Remediation by platform

nginx
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'" always;
Apache
<IfModule mod_headers.c>
	Header always set Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'"
</IfModule>
Caddy
header {
	Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'"
}
Cloudflare
Rules → Overview → Create rule → Response Header Transform Rule → Set static → Content-Security-Policy = default-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'
(Dashboard path as at 2026-09; Cloudflare reorganised these pages and may again.)
  • The always flag matters: without it nginx omits the header on error responses, which are exactly the ones an attacker aims for. Note also that any add_header in a location block discards every add_header inherited from the server block.

  • Shown because the response identified Apache. Put it in the virtual host rather than .htaccess: .htaccess is re-read on every request, and Header always set there runs too late for responses the server generates itself.

  • A Transform Rule adds the header at Cloudflare's edge, so it applies only to responses that reach visitors through Cloudflare. An origin that is reachable directly — by IP, or through a DNS record that is not proxied — still serves the response measured here without it. Setting it at the origin covers both paths.

Technical detail

‹detail›

This rule is graded on what the policy permits, not on whether the header exists: default-src * with no script governance parses, is present, and restricts nothing, so it fails here. A report-only policy also fails, because it enforces nothing — it is the right way to *develop* a policy and is not one yet.

The modern shape is a nonce with 'strict-dynamic', which avoids maintaining a host allow-list: script-src 'nonce-{random}' 'strict-dynamic'; object-src 'none'; base-uri 'none'.

Standards and references

Test this on your domain

Run the check that produces this finding, on its own, against any domain.

Open the web security headers checkerBuild the fix

Other web security headers checks