Your CSP is set. Does it actually stop anything?

Adding a Content-Security-Policy header feels like a finished job. The header is there, the security scanner stops complaining about a missing CSP, and you move on. The trouble is that a CSP is only worth the directives inside it, and a policy can be present, look reasonable at a glance, and still let an injected script run.

This is different from the two CSP problems people already know about. A site with no CSP at all has nothing. A site with 'unsafe-inline' has told the browser to run any inline <script> it finds. The case here is the quieter one: a CSP that has neither of those obvious holes but still leaves a door open through a directive you didn't think to set.

The three gaps that hollow out a policy

There are three places a CSP commonly goes weak. None of them is a typo. Each is a directive that's either too permissive or simply absent.

The first is a wildcard or scheme-only source in the script context. script-src 'self' https: reads as restrictive until you notice that https: matches every HTTPS origin on the internet. Any host an attacker can get a script onto, a hijacked CDN, a misconfigured S3 bucket, a domain they bought, now satisfies your policy. The same goes for a bare *. The allowlist is so wide it allows almost everything.

The second is a missing base-uri. The <base> tag rewrites where every relative URL on the page resolves to. If an attacker can inject one (and a CSP that allows it is one that doesn't set base-uri), they can repoint every relative <script src> on your page at a server they control, without ever needing an inline script. base-uri 'self' closes this, and most policies leave it out because nothing visibly breaks when it's missing.

The third is a missing object-src with no default-src to fall back on. object-src governs <object>, <embed>, and the legacy plugin surface. Leave it unset and an injected <embed> can load attacker content. The fix is one token, object-src 'none', and it's almost always safe because nobody ships Flash anymore.

request
GET / HTTP/1.1 Host: app.yoursite.com
response
HTTP/1.1 200 OK
Content-Security-Policy: default-src 'self';
script-src 'self' https:;
(no base-uri, no object-src)
It has a CSP. The https: source matches every HTTPS host, and the missing base-uri leaves <base> injection open.

Why a strict-looking policy gets graded as weak

The reason this is worth flagging on its own is that the header passes a shallow check. A reviewer sees Content-Security-Policy:, sees 'self' in there, and assumes it's doing its job. An attacker who has already found an HTML injection point reads the same header and sees the way through.

There is one nuance that matters, because getting it wrong produces a false alarm. When your script context contains 'strict-dynamic', the browser deliberately ignores host and scheme allowlists like https: and loads scripts purely by the nonce or hash chain. In that case the wildcard host source isn't doing anything, so flagging it would be a finding on a value the browser already disregards. A bare nonce or hash on its own works differently: it's additive to host sources, not a replacement, so script-src 'nonce-xyz' https: will still load a script from any HTTPS host. But a policy that's reaching for nonces or hashes is one written by someone on the modern CSP path, so this check stays conservative and suppresses the wildcard complaint whenever a nonce, a hash, or 'strict-dynamic' is present in the script source. The wildcard only counts against you when none of those is in play.

This is a low-severity finding, not a critical one. A weak-but-present CSP is hygiene: it raises the cost of an injection bug you may not even have. It's worth fixing precisely because the fix is cheap and the policy was supposed to be your backstop.

Content-Security-Policy:
default-src 'self';
script-src 'self' https:;
The strict version pairs a nonce with 'strict-dynamic', so the browser loads scripts by the nonce chain and ignores host allowlists, plus base-uri and object-src are locked.

Reading your own policy from outside

Your CSP travels in a response header on every page, which means anyone can read it without logging in, and so can a scanner. SurfaceCheckr fetches your site, parses the policy you actually serve, and checks the three gaps: a wildcard or any-scheme script source where the script context isn't already using a nonce, a hash, or 'strict-dynamic', a missing base-uri, and a missing object-src with no default-src behind it. It stays quiet about the wildcard on a policy that's built around nonces or hashes, because that's a policy on the strict path and nagging it would be a finding on a near-non-issue. What you get is a read of whether the header you set is the protection you think it is, taken from the same place an attacker reads it.

If you want the directive-by-directive version of what a CSP is for, the unsafe-inline article covers the most dangerous source value, and the small security headers piece covers the one-line headers that sit alongside it.

Find it before someone else does.

Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.