Your CSP is strong and broken, and a grade will not tell you
On this page
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?
What every tool answers
How strong is this policy? Read the header, check it for unsafe-inline, wildcards and a missing object-src, return a grade. Everything needed is in the one line of text.
What a grade cannot answer
Does it break the site? Nothing in the header says. A directive is only wrong in the presence of the thing it blocks, so answering means fetching the page and checking every resource it pulls.
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, so a grader reads 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
A browser already computes this answer. Content-Security-Policy-Report-Only asks it to check
every resource on the page against the policy and report what it would have blocked, which is the
question, and it is the standard advice for exactly that reason.
It arrives somewhere nobody is standing. A violation report needs real traffic to produce it, a collector to receive it and a person to read it, and a header that has been report-only for two years is what becomes of the third.
Draugr runs the same comparison on every scan, with none of that. Its headers control fetches the
page, lists the inline scripts, event handlers and styles and the scripts, stylesheets, images and
fonts it loads, and checks each against the policy the response carried. Here it is against a
checkout page with an inline handler, a script from a CDN and a third-party font:
WHAT WILL NOT LOAD
draugr scanscript-src, or stop loading from it.font-src, or stop loading from it.What comes back is the list of things that will not load, each with its fix, before the change merges. The run still passes, because a policy that breaks its own page is a fault to fix and exposes nobody, so these findings never fail a security gate.
A policy sent as Content-Security-Policy-Report-Only gets the same check, worded as what
enforcing it would block, which is the list a team needs before switching it on. Where a policy
allows 'unsafe-inline', the finding lists what on the page depends on it, or states that nothing
does, and removing it stops being a guess.
The check reads the markup the server sends. A script that another script injects, or a WebSocket one opens, exists only at runtime and is out of its reach. The failures that strand a policy in report-only are inline handlers and a forgotten CDN, and those are sitting in the markup.
The headers where grading is enough
The rest of the header set is simple, and worth doing precisely because it is:
| Header | What it settles | What getting it wrong costs |
|---|---|---|
Strict-Transport-Security | present, long enough, preferably preloaded | one plaintext request is enough to intercept |
X-Content-Type-Options: nosniff | one value, no tradeoff | an upload the browser decides to run as script |
Referrer-Policy | anything other than unset | your URLs, and whatever is in them, handed to third parties |
X-Frame-Options / frame-ancestors | who may frame you | clickjacking |
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, the reason is in the page, and a scan that reads the page against the policy names it.
The HTTP security headers guide covers what each header does, and DAST covers the active half of checking a running service.