Research
Published August 17, 2026 · 521,442 domains scanned
State of the Web 2026
An annual, anonymized census of client-side security across the sites we scan. It measures how widely the protective HTTP response headers, Content-Security-Policy, the modern browser policies and the Reporting API are adopted, how well they are configured, which mistakes are most common, and which fixes matter most.
Every figure below is a population share over the 521,442 domains scanned for this edition. No figure names an individual site.
The web keeps its configuration tidy and leaves it unsafe. Configuration quality, how cleanly each header and policy is authored, averages 95.8 out of 100. Security, whether that configuration actually protects anyone, averages 28.2.
Those are two different axes, not two grades of one thing. A policy can be flawlessly authored and still wide open, and across half a million domains that is the normal case.
Domains scored for this edition, each one with a GET request.
Content-Security-Policies collected and evaluated.
Key findings
- Almost nothing resists script injection. 83 domains out of 521,442 pass, and 99.3% are exposed.
- A quarter of the web ships a Content-Security-Policy; 4.4% of those policies pass. A passing CSP exists on 0.97% of all domains.
- Nonces have not landed. 5.4% of policies use one, against 37.1% that still allow
unsafe-inline. - Reporting is a CDN artifact. 32.0% of domains collect network-error reports because a CDN switched it on; 2.2% collect their own CSP violations.
- The ceiling is reachable. The best 150 domains average 88.6 out of 100, against a corpus average of 28.0.
How does the web score overall?
Plot every domain by its overall score and the curve has one story to tell: it piles up near the bottom. Roughly six in ten sites land in the 20-to-40 band, and 86.1% score under 40 overall.
The crowding is sharper still inside that band. The 20-to-30 range alone holds over a third of the web (35.4%). Above 60 you find about 2% of sites, and the whole 90-to-100 range is 21 domains out of 521,442.
This is not a healthy population with a struggling tail. The struggling part is the population.
The waffle below puts the whole web in 100 squares, shaded by score band. Watch the block of red across the top: those are the two lowest bands, which together fill 86 of the 100 squares.
0-20: 29.0% of domains. 20-40: 57.1% of domains. 40-60: 11.8% of domains. 60-80: 1.8% of domains. 80-100: 0.2% of domains.
Each square is about 1% of all 521,442 domains, shaded by its overall-score band.
That curve is the rest of the report in one shape. Before reading it header by header, here is how each score was built.
How we measured this
Each of the 521,442 domains received a single anonymous GET request over HTTPS, and only its response headers were analyzed. The assessment is passive and non-intrusive: no payloads, no fuzzing, no state-changing requests.
Every header, policy and directive is checked on two separate 0-100 axes: security, does it protect, and quality, is it cleanly authored. Those two combine into the overall score.
Presence is not safety. A header being delivered and parseable is not the same as it being correct, and quality is a different question from security rather than a softer version of it.
A tidy Content-Security-Policy that still allows unsafe-inline scores high on quality and low on security. That single example is the whole report in one line.
How a domain is scored
We grade every header, directive, policy, reporting endpoint and attack concept on its own, from 0 to 100. Each one starts clean and loses points for the problems found on it, and the single worst problem sets its score.
How far a problem pulls a score down depends on its severity. Every finding lands in one of five levels, from an open hole to a gentle nudge:
Those per-item scores roll up into the category and overall scores by weight, so the controls that matter most for security count most. A weak script-src moves the needle far more than a missing Referrer-Policy, and the same weighting folds the relevant headers, directives and policies into each attack-resistance score.
One rule does the heavy lifting at the bottom of the scale. A required control that is simply missing scores 0 on that item, every time.
What separates a missing critical header from a missing minor one is not the score but the weight it carries. The mandatory one drags the total down hard; an optional one barely moves it.
What this data can and cannot tell you
Every number here describes response headers at scan time, from one request, from one place on the network. That is a real limit and it cuts in both directions, so it is worth stating plainly before the findings.
We under-count some protections. A site that sets a policy only on authenticated pages, or that varies headers by route, is judged on the one response we saw. Controls that live in HTML rather than headers, such as a meta-delivered policy on a page we did not fetch, are invisible to us.
We also under-count some risks. Header analysis cannot see whether a page loads an unpinned script, whether an inline handler bypasses the policy in practice, or whether a nonce is regenerated per response. A passing header is a floor, not a clean bill of health.
The population is what we scan, not a ranked sample of the web. It skews toward domains that resolve, answer on 443 and return a document, and it is not weighted by traffic. A figure here is a share of domains, never a share of page views.
One external check is worth having. The HTTP Archive's Web Almanac put CSP adoption at 21.9% of mobile home pages in 2025, measured on a different corpus with a different crawler; we measure 22.0%. The two do not have to agree, and the fact that they do is a reason to trust the shape of what follows.
Two axes, five severities, a weight on every control, and a stated limit on what a header can prove: that is the method. The sections that follow read its output, first the big attack classes, then header by header.
How exposed is the web to the big client-side attacks?
Roll every relevant header, directive and policy into one resistance score per attack class and the web's defenses nearly vanish. Every vector leaves 88% or more of sites exposed.
Full protection is a rounding error. Just 83 domains pass on script injection and 97 on data exfiltration; supply-chain tampering is stopped on a single domain in the entire corpus.
The relative exception is clickjacking, where 5.2% pass, and only because the fix is one old, simple header. Even there, almost nine sites in ten are open.
Each ring below rolls the headers, directives and policies that defend against one attack class into a single test, then counts a domain as exposed when that defense is missing or too weak. The green slice is the strict bar: only a full pass counts as protected.
Corpus exposure
| Protected | 27,096 (5.2%) |
|---|---|
| Partial | 31,412 (6.0%) |
| Exposed | 462,934 (88.8%) |
Clickjacking
Clickjacking loads your page inside an attacker's frame and tricks users into clicking controls they cannot see. One old header closes it: X-Frame-Options, or a CSP frame-ancestors directive.
The donut shows the catch: this is the best-defended vector, and it is still mostly open. Watch the green slice, the only one of the four big enough to read.
Because the fix is a single line copied around for years, this is the one vector where a real share of the web is protected. About one site in 19 passes (5.2%), and roughly one in nine has some defense. The other 88.8% are exposed.
It sets the bar for what follows. The rest of the big client-side attacks need a whole maintained policy, and there almost no site measures up. Even clickjacking, the easy one, averages 18 out of 100 in resistance.
Corpus exposure
| Protected | 83 (0.0%) |
|---|---|
| Partial | 3,491 (0.7%) |
| Exposed | 517,868 (99.3%) |
Script injection
The classic cross-site scripting attack: an attacker gets their own code to run inside your page, where it can read the DOM, lift session tokens and rewrite forms. The one thing that reliably contains it is a strict Content-Security-Policy, a nonce or hash based script-src with strict-dynamic, backed by Trusted Types or enforced Subresource Integrity, with the injection sinks (base-uri, object-src) locked down.
The donut is almost entirely red. A domain counts as exposed when that strict path is open, which is nearly everywhere.
Out of 521,442 domains, 83 pass, and fewer than one site in 100 has even partial defense (0.7%). The average resistance score is 3 out of 100.
That is effectively the whole web failing the one test that matters most for the most common client-side attack.
Supply-chain tampering
Most pages pull in third-party scripts from vendors and CDNs. If one of those scripts is altered upstream, the modified code runs in your page with full access. This is the pattern behind Magecart card-skimming.
Subresource Integrity, required through an Integrity-Policy header or require-sri-for, is what catches a script that changed behind your back. A domain counts as exposed when integrity is not enforced and scripts are trusted by origin alone.
The stat collapses to a single number because protection here is too rare to draw. Exactly one domain in the whole corpus passes, the average resistance is 3 out of 100, and the web is effectively 100% exposed.
A single compromised dependency would run unchecked almost everywhere.
Corpus exposure
| Protected | 97 (0.0%) |
|---|---|
| Partial | 7,324 (1.4%) |
| Exposed | 514,021 (98.6%) |
Cross-origin data exfiltration
Once a script runs on your page, the next question is where it can send what it reads. A CSP that pins connect-src, img-src and form-action to known hosts boxes in the outbound channels: fetch, image beacons and form posts.
A domain counts as exposed when those channels are open, so an injected script could quietly ship form data and tokens to any origin it likes.
97 of 521,442 domains pass and 98.6% are exposed. The partial defenses come from the 1.4% of sites that pin some channels but not all.
This vector carries the highest average resistance of the four, 45 out of 100, and that number needs reading with care. It is partial credit, not safety: a policy that pins images but leaves connect-src open scores well and stops nothing.
Across all four classes the verdict is the same. Full protection is a rounding error everywhere, the one vector with real protection owes it to a single legacy header, and the resistance averages are propped up by partial defenses rather than by anyone closing the gap. The next section steps back from the attacks to ask which protective headers the web actually sends.
Which protective headers does the web actually send?
Rank the protective response headers by how many sites send them and you get a staircase down from the automatic to the deliberate. Cache-Control tops it at 68.4%, and it is there because performance tooling sets it rather than because anyone chose it.
The security headers proper sit in the middle, each on roughly two in five sites: X-Content-Type-Options at 42.2%, X-Frame-Options at 39.3%, Strict-Transport-Security at 38.8%. The cross-origin headers, which take real thought, fall to 1.4% at the bottom.
Adoption is only half the story. For most active headers, the sites that send them pass our checks: X-Content-Type-Options passes on 99.9% of its senders, and X-Frame-Options and Cross-Origin-Resource-Policy pass on every single one.
These headers are near-binary switches, though. Passing them is easy, so the real signal is how few sites send them at all. Referrer-Policy passes on 84.5% of its senders, Cache-Control on 85.6%.
One header breaks the pattern. Nearly two in five sites send Strict-Transport-Security, but fewer than half of those send it correctly: 42.3% of HSTS senders pass, while the rest ship a short max-age or skip includeSubDomains and preload.
That gap is the whole story of this section. Counted against the entire corpus, fully correct HSTS reaches just one site in six (16.4%). It is the one broadly adopted header where adoption and correctness come apart.
The first table below is that staircase: each active header by adoption and by how its senders score. The second lists the retired headers still in the wild, where sending them is the problem.
Protective response headers
Share of all 521,442 scored domains that send each header, and how the senders score.
| Header | Adoption | PassPartialFailed |
|---|---|---|
| Cache-Control | 85.6 / 0.0 / 14.4 | |
| X-Content-Type-Options | 99.9 / 0.0 / 0.1 | |
| X-Frame-Options | 100.0 / 0.0 / 0.0 | |
| Strict-Transport-Security | 42.3 / 32.2 / 25.4 | |
| Referrer-Policy | 84.5 / 0.0 / 15.5 | |
| Cross-Origin-Resource-Policy | 100.0 / 0.0 / 0.0 |
How widely each protective response header is sent, ranked by adoption.
Retired headers
Headers the platform has dropped, ranked by how many domains still send them.
| Header | Why it was retired | Still sent by |
|---|---|---|
| X-XSS-Protection | Drove the XSS auditor every browser has since removed; only 0 is safe. | |
| Expect-CT | Certificate Transparency reporting, redundant since CT became mandatory. | |
| Feature-Policy | Renamed and replaced by Permissions-Policy. | |
| Public-Key-Pins | One wrong pin could lock users out of the site; replaced by Certificate Transparency. |
Headers browsers have retired, why each was dropped, and how many domains still send them.
The header that makes browsers less safe
Most missing headers cost you protection. One header costs you protection by being there. X-XSS-Protection drove a browser feature, the XSS auditor, that every modern engine has since removed.
Turning it on (1 or 1; mode=block) created the very cross-site information leaks it was meant to stop. The only safe value left is 0, which switches the dead feature off.
Yet more than a fifth of the web still sends the header (21.2%, 110,371 sites). For these sites adoption is the regression: the fix is to delete the header, not configure it.
Worse, more than nine in ten of those senders use the unsafe enabled value (91.6%, 101,139 sites), the exact mode that was the reason browsers killed the feature.
The two tiles below read inverted. Presence is the failure, so adoption shows in amber, and the second tile is the share of senders stuck on the unsafe value.
still send X-XSS-Protection
110,371 of 521,442 domains keep a header every current browser has removed.
of those use the unsafe value
101,139 domains leave the filter on (1 or 1; mode=block), the mode that opened cross-site information leaks. The safe value is 0.
How many domains still send X-XSS-Protection, and how many of those use the unsafe enabled value.
What your headers give away
Headers protect, but they also disclose. By default a lot of stacks announce themselves in the response: Server, X-Powered-By and their kin name the software, and often the exact version, running behind the site.
None of this is required to serve a page, and all of it shortens an attacker's work. Naming the technology narrows the search; naming the version turns the header into a direct lookup against published vulnerabilities for that release.
Roughly two in five sites name their stack (39.1%), and over half of those print the version too (54.9% of the disclosers), leaving about one site in five exposing the exact release it runs (21.5% of the whole corpus).
The two tiles below split that disclosure: the share of all sites that name their technology, and the narrower share that also leak the version.
name their technology in a header
203,896 domains advertise what they run through Server, X-Powered-By and similar headers.
also expose the exact version
111,896 domains add the version number too, turning a banner into a ready-made lookup of known vulnerabilities.
Across the whole corpus, how many domains disclose their technology in a header, and how many also expose its version.
How safe are the cookies the web sets?
A cookie carries the session, so its flags decide how easily that session can be stolen or sent where it should not go. Three attributes do the work: Secure keeps the cookie off plaintext connections, HttpOnly hides it from JavaScript and so from an injected script, and SameSite controls whether it rides along on cross-site requests.
This figure counts cookies rather than sites. Across the 492,692 cookies in the corpus, about half carry each protective flag: Secure on 55.0%, HttpOnly on 49.5%, SameSite on 46.8%. Fewer than half declare SameSite at all.
The SameSite value is the sharper twist. Of the cookies that set it, most choose None, the value that opts back out of cross-site protection. At 49.7% it is the plurality, just ahead of Lax at 44.9% and far ahead of Strict at 5.3%.
So the modal hardened-cookie choice is the weakest one. Counted against every cookie in the corpus, None lands on about one in four (23.2%), while Strict stays a rounding error.
The table below shows the share of cookies using each flag, and the donut below it breaks down the SameSite value among those that set one.
Cookie protections
Share of the 492,692 cookies in the corpus that set each attribute.
| Attribute | What it does | Adoption | Cookies |
|---|---|---|---|
| Secure | Only sent over HTTPS, never on a plain connection. | 271,223 | |
| HttpOnly | Hidden from JavaScript, so an injected script cannot read it. | 243,865 | |
| SameSite | Limits when the cookie rides along on cross-site requests. | 230,548 |
SameSite value, when set
| None | 114,526 (49.7%) |
|---|---|
| Lax | 103,616 (45.0%) |
| Strict | 12,285 (5.3%) |
Of the cookies in the corpus, the share setting each protective attribute, and the SameSite value chosen by those that set one.
Across all four figures the same fault repeats: a header sent is not a header set correctly. The easy headers are nearly perfect; the load-bearing ones are not. Where the value carries real security weight, HSTS, SameSite and the leftover retired headers show how often the choice works against the site.
That leads straight into the one control built to contain script injection, and how the web builds it.
How is Content-Security-Policy actually built?
Content-Security-Policy is the one control on the web that reliably contains script injection once it happens. About a quarter of sites send one, and almost none of those send a safe one.
The four donuts below frame the whole problem: how many domains ship a CSP, how those policies score on security, how they are delivered, and whether they enforce or only watch.
Adoption
| Ships a CSP | 114,974 (22.0%) |
|---|---|
| No CSP | 406,468 (78.0%) |
Security outcome
| Pass | 5,049 (4.4%) |
|---|---|
| Partial | 5,250 (4.6%) |
| Failed | 104,675 (91.0%) |
Delivery method
| Header | 113,398 (96.0%) |
|---|---|
| Both | 1,576 (1.3%) |
| Meta tag | 3,104 (2.6%) |
Mode
| Enforced | 104,416 (90.8%) |
|---|---|
| Both | 2,951 (2.6%) |
| Report-only | 7,607 (6.6%) |
How many domains ship a Content-Security-Policy, how those policies score, and how they are delivered and enforced.
Adoption is the easy half. 22.0% of the web ships a CSP (114,974 of 521,442 domains), and those policies are clean: they average 98 out of 100 on quality.
Then the security check runs and the floor drops out. Of the policies we evaluate, 91.0% fail (104,675 sites) and only 4.4% pass (5,049 sites).
The same policies that score 98 on clean authoring average 29 on security. Clean, and useless.
These are real policies, not experiments. Almost all run in enforce mode rather than report-only (90.8%), and 96.0% arrive in a header, with only 2.6% relying on a meta tag alone. The header is present; the protection is not.
At corpus scale, a passing CSP exists on 0.97% of all domains. Less than one site in a hundred has the control that would contain the most common client-side attack.
Which directives do policies set, and how well?
A Content-Security-Policy is a list of directives, and they do not carry equal weight. Sites get the cheap ones right and the dangerous ones wrong.
frame-ancestors leads adoption at 54.8% of CSP sites and passes 76.8% of the time, because it does one narrow job. script-src, the directive that actually gates injected scripts, appears on 39.4% of CSP sites and fails 89.4% of the time (41,560 of 46,469 uses).
That pattern holds down the table. The directives that constrain scripts and styles fail hardest (style-src passes 8.2% of the time, script-src-elem 6.4%), while the structural ones pass easily: base-uri passes 85.5%, and upgrade-insecure-requests passes everywhere it appears.
The hard part of CSP is the two or three directives that actually contain an attack, not the act of listing them. The modern hardening directives barely register at the bottom of the table: require-trusted-types-for appears on 276 CSP sites, trusted-types on 144.
The table below ranks every canonical directive by how often it appears, then scores each one: of the times it is used, how many pass, partially pass, or fail. Watch the gap between the high-usage *-src directives and their failure rates.
CSP directives
Share of the 118,078 CSP-delivering domains that set each directive, and how those uses score.
| Directive | Used | PassPartialFailed |
|---|---|---|
| frame-ancestors | 76.8 / 12.4 / 10.8 | |
| default-src | 55.4 / 1.7 / 42.8 | |
| script-src | 10.6 / 0.0 / 89.4 | |
| img-src | 17.1 / 1.8 / 81.1 | |
| style-src | 8.2 / 47.2 / 44.5 | |
| font-src | 71.9 / 0.5 / 27.6 | |
| upgrade-insecure-requests | 100.0 / 0.0 / 0.0 | |
| connect-src | 20.7 / 0.0 / 79.3 | |
| frame-src | 26.0 / 13.2 / 60.9 | |
| object-src | 71.0 / 0.0 / 29.0 | |
| base-uri | 85.5 / 0.0 / 14.5 | |
| media-src | 61.7 / 1.0 / 37.3 | |
| form-action | 64.6 / 3.2 / 32.2 | |
| worker-src | 66.7 / 0.0 / 33.3 | |
| child-src | 17.7 / 28.8 / 53.5 | |
| report-uri | 99.9 / 0.0 / 0.1 | |
| manifest-src | 92.8 / 0.1 / 7.2 | |
| block-all-mixed-content | 100.0 / 0.0 / 0.0 | |
| script-src-elem | 6.4 / 0.0 / 93.6 | |
| report-to | 99.7 / 0.0 / 0.3 | |
| style-src-elem | 10.0 / 45.3 / 44.7 | |
| script-src-attr | 47.6 / 0.0 / 52.4 | |
| style-src-attr | 9.3 / 85.4 / 5.3 | |
| require-trusted-types-for | 100.0 / 0.0 / 0.0 | |
| sandbox | 7.4 / 0.0 / 92.6 | |
| prefetch-src | 73.5 / 5.1 / 21.4 | |
| trusted-types | 92.4 / 0.0 / 7.6 | |
| fenced-frame-src | 52.6 / 10.5 / 36.8 | |
| navigate-to | 96.8 / 0.0 / 3.2 | |
| plugin-types | 100.0 / 0.0 / 0.0 | |
| referrer | 100.0 / 0.0 / 0.0 | |
| reflected-xss | 100.0 / 0.0 / 0.0 | |
| require-sri-for | 0.0 / 100.0 / 0.0 | |
| webrtc | 100.0 / 0.0 / 0.0 |
How often each CSP directive appears in policies, and how well those uses score.
What hardens a policy, and what weakens it
Whether a policy protects comes down to the keywords inside it, and CSP authors reach for the wrong ones. The weakening keywords outnumber the hardening ones by a wide margin.
unsafe-inline shows up on 37.1% of CSP sites and unsafe-eval on 32.6%. Nonces, the headline hardening mechanism, reach 5.4%. Roughly seven times as many policies allow inline scripts outright as lock them down with a nonce.
It gets worse below the headline. strict-dynamic, the keyword that makes a nonce-based policy genuinely hard to bypass, sits on 2.7% of CSP sites (3,186 policies). Trusted Types, the strongest DOM-injection defense the platform offers, is enforced on 275.
The weakening list also runs deeper than the two famous keywords. More than one policy in five allows a known JSONP endpoint (22.5%) or a known script gadget (20.9%), both of which hand an attacker a way around an otherwise reasonable allowlist.
The two tables below split the mechanisms that harden a policy from the ones that weaken it, each as a share of CSP-delivering sites. Read them as a pair: the green column is short, the red column is not.
Hardening mechanism
Share of the 118,078 CSP-delivering domains.
| Hardening mechanism | Domains | Hardening |
|---|---|---|
| upgrade-insecure-requests | 39.3k | |
| Nonces | 6.3k | |
| strict-dynamic | 3.2k | |
| Hashes | 2.2k | |
| Trusted Types (enforced) | 275 | |
| Trusted Types (declared) | 144 |
The mechanisms that strengthen a policy, each as a share of CSP-delivering domains.
Weakening mechanism
Share of the 118,078 CSP-delivering domains.
| Weakening mechanism | Domains | Weakening |
|---|---|---|
| unsafe-inline | 43.8k | |
| unsafe-eval | 38.5k | |
| JSONP endpoint | 26.6k | |
| Script gadget | 24.7k | |
| Subdomain wildcard | 22.5k | |
| Scheme source | 21.1k | |
| Shared host | 13.3k | |
| Full wildcard * | 4.4k | |
| wasm-unsafe-eval | 1.5k | |
| unsafe-hashes | 740 |
The mechanisms that weaken a policy, each as a share of CSP-delivering domains.
What goes wrong in the policies that exist?
Even where policies are messy, the mess is mostly harmless. The most common authoring mistake reaches 12.3% of CSP sites, and it is carrying a deprecated directive copied from an old template.
Below it the list drops into single digits: a duplicate source value (8.2%), a keyword used in a directive that ignores it (6.1%), a source already covered by a broader one (5.9%). These are hygiene problems, not holes.
The ones with real teeth are rare, which is exactly why they survive. About one CSP site in 325 separates its directives with commas instead of semicolons, which throws no error a developer would ever see. It quietly collapses the policy into nonsense.
CSP gives authors almost no feedback, so these mistakes are never caught. Nothing in a browser tells you that your policy parsed into something you did not write.
The table below ranks the ten most common authoring mistakes by how many CSP sites they hit, as a share of CSP-delivering sites.
CSP authoring mistakes
Share of the 118,078 CSP-delivering domains hit by each mistake.
| Authoring mistake | Domains | Share of CSP sites |
|---|---|---|
| Deprecated directive | 14.6k | |
| Duplicate value in a directive | 9.7k | |
| Keyword not valid in that directive | 7.2k | |
| Source already covered by a broader one | 7k | |
| Source value with no effect | 3.1k | |
| Directive allows localhost | 2.4k | |
| Policy sent in both a meta tag and a header | 1.6k | |
| Keyword missing its quotes | 1.3k | |
| Unknown directive | 796 | |
| Directive declared with no value | 709 |
The most common authoring mistakes in the policies that exist, by how many CSP sites they hit.
CSP adoption is real, but a present header is not a working defense. Nine policies in ten fail, and the directives that stop script injection are exactly the ones authors get wrong. The next section steps back from CSP to ask how the other modern policies are deployed.
How widely are the other policies deployed?
The most-deployed policy on the web is not a security control. Network Error Logging reaches 32.0% of domains, ahead of Content-Security-Policy at 22.0%, and it gets there because CDNs switch it on rather than because operators chose it.
Below CSP the modern headers thin out fast. Permissions-Policy reaches 9.7% and COOP 8.7%, roughly one site in eleven. COEP, the second half of cross-origin isolation, lands on 0.8% of sites.
Because true isolation requires both COOP and COEP, that COEP figure is the real ceiling no matter how many sites set COOP alone. COEP is the scarcer of the pair by a factor of eleven, so almost every COOP deployment cannot reach isolation at all.
The genuinely new policies barely register. Document-Policy sits at 0.1% (584 domains), while Integrity-Policy and Connection-Allowlist are a literal handful, 10 domains and 3. These are shipped browser features with essentially no field adoption, a frontier worth naming so next year has a baseline.
The table below ranks each policy by the share of the corpus that deploys it, then splits each one by mode: enforce, both, or report-only. Notice that report-only is all but abandoned; every widely-set policy is enforced from day one.
Modern policy adoption
Share of all 521,442 scored domains that deploy each policy.
| Policy | Deployed | EnforcedBothReport-only |
|---|---|---|
| Network Error Logging | 100.0 / 0.0 / 0.0 | |
| Content-Security-Policy | 90.8 / 2.6 / 6.6 | |
| Permissions-Policy | 99.9 / 0.0 / 0.0 | |
| Cross-Origin-Opener-Policy | 96.7 / 3.0 / 0.3 | |
| Cross-Origin-Embedder-Policy | 63.9 / 32.7 / 3.4 | |
| Document-Policy | 99.3 / 0.2 / 0.5 | |
| Connection-Allowlist | 33.3 / 0.0 / 66.7 | |
| Integrity-Policy | 50.0 / 0.0 / 50.0 |
How widely each modern policy is deployed, and for each one whether its deployers enforce it, run both modes, or only monitor it.
Deployment is only half the picture. The chart below shows, of the sites that deploy each policy, how many pass our security checks, land in partial, or fail outright, sorted so the worst failure rate sits on the left.
One bar stands apart. CSP fails on 91.0% of the policies that get evaluated and passes on 4.4%. Every other widely deployed policy passes almost every time it is set: Permissions-Policy at 100%, NEL effectively 100%, COOP at 94.8%.
COEP is the interesting middle case. It passes on only 15.6% of its deployers, with the other 84.4% landing in partial, because setting COEP without the COOP value that completes isolation gets you a header and not the guarantee.
Part of the CSP gap is what the policy can express. CSP is graded on a hard rubric, a rich allowlist that can be present yet insecure. The others are close to binary, where present and well-formed is a pass, so the contrast measures the difficulty of the policy as much as the operator's skill.
Read the smallest bars with care. The Document-Policy, Integrity-Policy and Connection-Allowlist rates rest on a few deployers each, not a population, so their bars are anecdotes, not trends.
| Policy | Pass | Partial | Failed |
|---|---|---|---|
| CSP | 4.4% | 4.6% | 91% |
| NEL | 100% | 0% | 0% |
| Permissions | 100% | 0% | 0% |
| COOP | 94.8% | 5.2% | 0% |
| COEP | 15.6% | 84.4% | 0% |
| Document | 100% | 0% | 0% |
| Connection | 100% | 0% | 0% |
| Integrity | 40% | 60% | 0% |
Of the sites that deploy and can be evaluated for each policy, how many pass, partially pass, or fail our security checks. Bars are sorted by failure rate.
CSP is the one widely deployed policy that mostly fails. A fifth of the web reaches for it, keeps it clean enough to enforce, and still leaves it open. The next section asks who can even see those failures when they happen.
Can anyone actually see the violations?
On paper, about a third of the web is wired up to report problems back to its operators. Almost all of that is network-error logging, switched on automatically by CDNs rather than chosen by any site owner.
Strip the auto-enabled logging out and the deliberate signal is small. Network Error Logging is set up on 166,629 domains; every other report type a site would actually choose adds up to 17,494 domains between them.
The report that matters most is the rarest of the common ones. 2.2% of the corpus collects its own Content-Security-Policy violations: 11,526 domains, the only feedback that tells you your policy is breaking real pages.
Reporting is also the rare place where setup and delivery come apart, and the newer policies are where it breaks. Of the 1,596 domains that wire up COOP reporting, 89.8% have it broken, and COEP reporting is broken on 95.3% of the 1,523 that try.
CSP reporting is in better shape: 97.3% of the domains that set it up receive reports. The web is not watching its own violations, and where it tries on the newer policies it usually misconfigures the endpoint.
What counts as broken is worth stating precisely. A row is broken only when the policy names a destination the browser cannot deliver to; a policy that quietly falls back to the default group and finds nothing there is counted as not set up, never as broken.
That distinction is what sinks COOP and COEP. Neither can name a URL: both point at a report-to group that has to be declared separately in a Reporting-Endpoints header, so the ordinary failure is a line copied from a guide with its report-to="coop" intact and no group ever declared behind it.
CSP escapes most of this because report-uri names a URL inline, with no second header to forget. The gap between 97.3% working for CSP and 4.7% for COEP is largely that one design difference, not a difference in how carefully the two were set up.
The table below ranks each report type by the share of the corpus that sets it up, then splits each row into working and broken endpoints. Two denominators sit side by side: the set-up share is of the whole corpus, but the working-versus-broken split is of that row's own set-up count.
Report collection
Set-up share is of all 521,442 domains; working and broken split each row's own set-up count.
| Report type | Set up to report | WorkingBroken |
|---|---|---|
| Network error | 100.0 / 0.0 | |
| CSP violation | 97.3 / 2.7 | |
| COOP report | 10.2 / 89.8 | |
| COEP violation | 4.7 / 95.3 | |
| Crash | 98.9 / 1.1 | |
| Deprecation | 98.9 / 1.1 | |
| Intervention | 98.9 / 1.1 | |
| Permissions-Policy violation | 99.5 / 0.5 | |
| CSP script-hash report | 94.4 / 5.6 | |
| Document-Policy violation | 71.4 / 28.6 | |
| Integrity violation | 100.0 / 0.0 | |
| Connection-Allowlist | 100.0 / 0.0 |
Each report type by the share of the corpus that sets it up. The working and broken columns split each row's own set-up count, not the corpus.
Report-To or Reporting-Endpoints?
There are two ways to tell a browser where to send reports. Reporting-Endpoints is the current standard; Report-To is the older header it replaced and is now deprecated.
The donut below looks only at the sites that declare either header, and the balance is lopsided. Of those sites, 97.5% still use the deprecated Report-To, and 2.5% have moved to the modern header.
That deprecated slice is the same CDN plumbing already counted above. Report-To sits on 32.3% of the corpus, the same share as network-error logging, because they are one CDN-injected mechanism rather than a choice made site by site.
The modern Reporting-Endpoints has barely landed: 4,281 sites, 0.8% of the corpus, roughly one site in 122.
Domains declaring a reporting header
| Reporting-Endpoints | 4,281 (2.5%) |
|---|---|
| Report-To (deprecated) | 168,672 (97.5%) |
Of the domains that declare a reporting-destination header, the split between the modern Reporting-Endpoints and the deprecated Report-To.
The web cannot see what it does not watch. Outside the logging CDNs switch on for it, deliberate reporting barely registers, and on the newest policies most of the sites that try get the endpoint wrong. The next section turns from diagnosis to prescription.
What should you fix first?
149 distinct findings is not a plan. Weight each one by how much damage it does and how many sites it reaches, and a short priority list falls out.
The three highest-severity recommendations all reduce to one sentence: ship a real CSP. The script-injection (99.2%), supply-chain (98.9%) and data-exfiltration (98.4%) findings each reach roughly 99% of sites.
All three are the same condition wearing three hats. A page with no real CSP restricts nothing, so it fails all three at once. That makes the highest-leverage move on the web also the most basic: deploy a policy, then tighten it.
Below those headline findings the list changes character. The fixes for sites that already have a CSP but weakened it reach far fewer domains: about one in six still lets object-src load plugins (18.0%) or leaves base-uri open (18.0%), and 17.3% let connect-src exfiltrate freely.
After that come the cheap missing-header recommendations that reach almost everyone but rarely raise the stakes, and finally the emerging policies almost no one ships.
One thing is worth stating because of what it is not. The catalogue contains no critical findings this year. Nothing in the corpus fires our top severity, and the damage is carried entirely by 22 high-severity findings, three of which reach nearly every site on the web.
The table below ranks the top recommendations by relevance, damage times reach, so the most damaging and most widespread fixes sit first.
Priority recommendations
The 149 findings in the catalog, weighted by severity and reach, over all 521,442 domains.
| Priority | Recommendation | Severity | Domains | Share of corpus |
|---|---|---|---|---|
| Do now | Add a strict nonce-based script-src with 'strict-dynamic'. | High | 517.4k | 99.2% |
| Do now | Add an Integrity-Policy header to require SRI for scripts. | High | 515.6k | 98.9% |
| Do now | Set connect-src to 'self' and your known hosts. | High | 513.2k | 98.4% |
| Fix your CSP | Set object-src 'none'. | High | 93.8k | 18.0% |
| Fix your CSP | Set base-uri 'none' (or 'self'). | High | 93.7k | 18.0% |
| Fix your CSP | Restrict connect-src to 'self' and your API/analytics hosts. | High | 90.5k | 17.3% |
| Fix your CSP | Drop 'unsafe-inline'; use a nonce with 'strict-dynamic'. | High | 42.8k | 8.2% |
| Fix your CSP | Remove 'unsafe-eval' from the directive. | High | 36.8k | 7.1% |
| Fix your CSP | Remove the dangerous scheme from the directive. | High | 26k | 5.0% |
| Fix your CSP | Remove the JSONP host from the directive. | High | 25.9k | 5.0% |
| Fix your CSP | Remove the script gadget host from the directive. | High | 24.2k | 4.6% |
| Fix your CSP | Replace the shared host with a dedicated one you control. | High | 13.3k | 2.5% |
| Fix your CSP | Remove the scheme source from the directive. | High | 8.1k | 1.5% |
| Fix your CSP | Move the CSP to the enforced header. | High | 7.6k | 1.5% |
| Fix your CSP | Replace default-src * with 'self'/'none' and set per-directive sources. | High | 3.6k | 0.7% |
| Fix your CSP | Replace the wildcard in the directive with a nonce policy. | High | 2.7k | 0.5% |
| Fix your CSP | Generate a fresh nonce for every response. | High | 1.2k | 0.2% |
| Recommended | Add a Permissions-Policy header disabling unused features. | Medium | 470.6k | 90.3% |
| Fix your CSP | Add a strict nonce-based Content-Security-Policy. | Medium | 403.4k | 77.4% |
| Recommended | Add a NEL header with a report-to group. | Medium | 354.8k | 68.0% |
149 findings weighted by severity and reach, the highest-leverage fixes first.
The whole catalogue collapses to one priority: get a real CSP in place, then close the directives that weaken the policies that already exist. Everything else is the long tail. The next section steps back from the failures to ask what the best-configured sites do differently.
How far apart are the best and the rest?
A small elite proves a near-perfect score is reachable. The best 150 domains average 88.6 out of 100; the whole corpus of 521,442 averages 28.0. That is a 60.6-point gap, more than three times the typical score.
Split that gap by axis and it turns out to be one axis. On quality the two populations are almost indistinguishable: 96.2 for the best cohort against 95.8 for everyone else, a difference of less than half a point.
On security they are not the same web. The best cohort averages 89.0 out of 100 against the corpus average of 28.2, and that single axis carries the entire 60.6-point gap.
So the elite are not tidier than everyone else. They are protected, and everyone else is not, while both keep their configuration in much the same order.
The three cards below compare the best-configured cohort against the whole corpus on the overall score. The section that follows breaks that one number down area by area.
The best-configured 150 domains against all 521,442, on the overall 0-100 score.
Where the best pull ahead, area by area
The cards above compare the two populations on one number; these radars show where the difference actually sits. Each one sets the best-configured cohort of 150 sites against the average across all 521,442 domains, every spoke a 0-100 sub-score.
The two series are not the same size. Green is fewer than 1 site in 3,400. Amber is the whole web, the real baseline.
The same shape repeats in every panel: a green ring pushed out near the rim, sitting on a collapsed amber core. The gap is categorical, not incremental.
On CSP the best cohort scores 98 against the average's 5, a twentyfold split and the widest of all. Attack resistance runs 80 against 18, and the policy headers 59 against 13, four to five times the average in both cases.
Two areas read against that grain. Security headers is the one place the average looks respectable at 74 out of 100, and only because the cheap headers nearly everyone already sends prop it up.
Reporting setup is the opposite. Even the best cohort scores 9 out of 100, so this is the one axis where being best still means almost nobody bothers. Read a spoke near zero with care: it can mean the control is simply absent, and where the best cohort sits at zero on a deprecated directive, dropping it is the correct move.
Security Headers
| Security Headers | Best cohort | Corpus average |
|---|---|---|
| Cross-Origin-Resource-Policy | 100/100 | 100/100 |
| Expect-CT | 100/100 | 100/100 |
| Feature-Policy | 100/100 | 100/100 |
| Public-Key-Pins | 100/100 | 100/100 |
| X-Content-Type-Options | 100/100 | 42/100 |
| Referrer-Policy | 100/100 | 24/100 |
| Strict-Transport-Security | 99/100 | 24/100 |
| Cache-Control | 98/100 | 89/100 |
| X-XSS-Protection | 82/100 | 89/100 |
Attack Resistance
| Attack Resistance | Best cohort | Corpus average |
|---|---|---|
| Clickjacking | 98/100 | 18/100 |
| Data Exfiltration | 88/100 | 45/100 |
| Script Injection | 82/100 | 3/100 |
| Supply Chain | 50/100 | 3/100 |
Reporting-API Policies
| Reporting-API Policies | Best cohort | Corpus average |
|---|---|---|
| Document-Policy | 100/100 | 100/100 |
| Integrity-Policy | 100/100 | 66/100 |
| Permissions-Policy | 99/100 | 10/100 |
| Cross-Origin-Opener-Policy | 67/100 | 8/100 |
| Content-Security-Policy | 52/100 | 29/100 |
| Cross-Origin-Embedder-Policy | 45/100 | 0/100 |
| Network Error Logging | 29/100 | 32/100 |
| Connection-Allowlist | 0/100 | 100/100 |
Reporting-API Setup
| Reporting-API Setup | Best cohort | Corpus average |
|---|---|---|
| CSP violation | 41/100 | 2/100 |
| Network error | 27/100 | 32/100 |
| Crash | 9/100 | 0/100 |
| Deprecation | 9/100 | 0/100 |
| Intervention | 9/100 | 0/100 |
| Permissions-Policy violation | 9/100 | 0/100 |
| COEP violation | 3/100 | 0/100 |
| COOP report | 3/100 | 0/100 |
| Connection-Allowlist | 1/100 | 0/100 |
| CSP script-hash report | 1/100 | 0/100 |
| Document-Policy violation | 1/100 | 0/100 |
| Integrity violation | 1/100 | 0/100 |
Content Security Policy
| Content Security Policy | Best cohort | Corpus average |
|---|---|---|
| default-src | 100/100 | 24/100 |
| font-src | 100/100 | 27/100 |
| form-action | 100/100 | 12/100 |
| frame-ancestors | 100/100 | 84/100 |
| manifest-src | 100/100 | 10/100 |
| report-to | 100/100 | 100/100 |
| require-trusted-types-for | 100/100 | 100/100 |
| upgrade-insecure-requests | 100/100 | 100/100 |
| worker-src | 100/100 | 14/100 |
| media-src | 99/100 | 15/100 |
| script-src | 99/100 | 6/100 |
| trusted-types | 99/100 | 92/100 |
| base-uri | 98/100 | 21/100 |
| block-all-mixed-content | 97/100 | 97/100 |
| child-src | 97/100 | 4/100 |
| frame-src | 97/100 | 13/100 |
| report-uri | 97/100 | 97/100 |
| webrtc | 97/100 | 97/100 |
| img-src | 96/100 | 12/100 |
| object-src | 96/100 | 20/100 |
| connect-src | 95/100 | 10/100 |
| style-src-elem | 91/100 | 35/100 |
| script-src-attr | 87/100 | 53/100 |
| style-src | 83/100 | 12/100 |
| style-src-attr | 81/100 | 48/100 |
| script-src-elem | 78/100 | 10/100 |
| sandbox | 74/100 | 30/100 |
| fenced-frame-src | 0/100 | 58/100 |
| navigate-to | 0/100 | 94/100 |
| plugin-types | 0/100 | 97/100 |
| prefetch-src | 0/100 | 75/100 |
| referrer | 0/100 | 97/100 |
| reflected-xss | 0/100 | 97/100 |
| require-sri-for | 0/100 | 45/100 |
Each radar plots every posture control except the hygiene headers, whatever its status. A control few domains set still shows at its (often low) average, so the distance between the best cohort and the average stays comparable. A spoke near zero does not always mean the control is configured badly: it can also mean domains simply do not set it. Green is 150 domains, amber is all 521,442.
The best sites settle the report's central question. A strict, secure posture is fully buildable, and a 1-in-3,400 sliver of the web has built it. The distance to everyone else is not a matter of tuning. It is the distance between sending a header and maintaining a whole policy, the same quality-high, security-low split traced here from the first chart to the last.
This report is produced by automated header analysis and reflects each target's configuration at scan time only. It is not a substitute for a penetration test, source-code review or runtime testing, and does not assert the absence of other vulnerabilities. All statistics are anonymized population shares over the domains we scan, and no individual site is named.
Where does your site land?
Scan your domain against the same checks that produced this report, then let CentralCSP collect the real Content-Security-Policy reports from your visitors' browsers so you can close the gap.
