← All articles

Your CSP is strong and broken, and no scanner will tell you

August 7, 2026 · Wilson Santos

Point any header scanner at a site and it will grade the Content-Security-Policy. Google's CSP Evaluator, Mozilla Observatory, the header templates in every dynamic scanner: they all measure roughly the same thing, which is how strong the policy is. Does it use unsafe-inline? Are the sources wildcarded? Is object-src locked down?

Useful. And it answers a question nobody is stuck on.

The thing teams are stuck on is different: does this policy break my site?

Why that gap exists#

Strength is a property of the policy text. You can evaluate it by reading the header, which is why every tool does.

Whether it works is a property of the policy and the page together. A directive is only wrong in the presence of the thing it blocks, an inline handler, a script from a CDN you forgot, a font, an image, a WebSocket. Answering it means fetching the page, enumerating what it loads, and checking each against the policy.

That is more work, and nobody does it. So every tool grades one half and stays silent on the other.

What that does to real teams#

The failure mode is predictable and extremely common.

Somebody writes a strong policy. It scores well. It also blocks three things the application legitimately does, so the site breaks in ways nobody notices immediately, an analytics call, a payment widget, a font on one page.

They roll it back and ship Content-Security-Policy-Report-Only instead, meaning to fix the violations and switch it on. That was two years ago. The header is still report-only, the reports go to an endpoint nobody reads, and the scanner grades the site as having a CSP.

The alternative outcome is a policy weakened until nothing breaks, unsafe-inline, unsafe-eval, a wildcard or two, which scores badly and at least ships. Of the two, the second is often the more honest position, and it grades worse.

A grader that cannot tell those three situations apart is grading the wrong thing.

What would help#

Cross-checking the policy against the page's own content: fetch it, list the scripts, styles, frames, fonts and connections it references, and report the ones the policy would block. Not a grade, a list of specific things that will not load.

That is a different kind of check from anything the header graders do, and as far as we can tell nobody ships it. It is on Draugr's backlog for exactly that reason.

The headers where grading is enough#

The rest of the header set is simple, and worth doing precisely because it is:

  • Strict-Transport-Security, present, long enough, and preferably preloaded.
  • X-Content-Type-Options: nosniff, one value, no tradeoff, no reason not to.
  • Referrer-Policy, a leak of URLs to third parties if unset.
  • X-Frame-Options / frame-ancestors, clickjacking, and cheap.

None of these interact with page content the way CSP does. They are pure configuration, they take minutes, and they are the ones a checklist settles.

Draugr checks these natively rather than shelling out, and tunes the list by whether a host is a browser surface or an API, flagging browser-only headers on a JSON endpoint is the kind of noise that teaches people to ignore a control.

The point#

A checklist is the right tool for the headers that are configuration, and the wrong tool for the one that is a relationship between a policy and a page.

If your CSP has been report-only for a year, no scanner in your pipeline is going to tell you why. That is worth knowing about the tools rather than concluding your policy is fine.

The HTTP security headers guide covers what each header does, and DAST covers the active half of checking a running service.