New: CentralCSP v2 is out, with full Reporting-API support. Read the changelog

Research

Published September 13, 2026 · 761,367 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 761,367 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 96.6 out of 100. Security, whether that configuration actually protects anyone, averages 29.2.

Those are two different axes, not two grades of one thing. A policy can be flawlessly authored and still wide open, and across three quarters of a million domains that is the normal case.

Security29.2/100
Quality96.6/100
761,367

Domains scored for this edition, each one with a GET request.

152,164

Content-Security-Policies collected and evaluated.

Key findings

  • Almost nothing resists script injection. 108 domains out of 761,367 pass, and 99.3% are exposed.
  • One site in five ships a Content-Security-Policy; 1.5% of those policies pass. A passing CSP exists on 0.29% of all domains.
  • Nonces have not landed. 5.3% of policies use one, against 38.1% that still allow unsafe-inline.
  • Reporting is a CDN artifact. 33.7% of domains collect network-error reports because a CDN switched it on; 1.9% collect their own CSP violations.
  • The ceiling is reachable. The best 150 domains average 92.4 out of 100, against a corpus average of 46.0.

How does the web score overall?

Plot every domain by its overall score and the curve has one story to tell: it bunches in the middle and never climbs. Six in ten sites land in the 40-to-60 band, a third sit below it, and 93.4% score under 60 overall.

Both ends are nearly empty. Not one domain in the corpus scores below 20, but only 6.6% clear 60, and the whole 80-to-100 band is 3,051 domains out of 761,367, four in every thousand.

That middle bulge is the two axes averaging out: near-perfect authoring dragged down by security that barely registers. A 46 is not half-protected. It is well-formed and open.

The waffle below puts the whole web in 100 squares, shaded by score band. Watch how little green there is: 33 red squares and 61 amber ones carry the whole population, and just 6 squares reach past 60.

0-200.0% (0)20-4032.7% (248,681)40-6060.7% (462,466)60-806.2% (47,169)80-1000.4% (3,051)

0-20: 0.0% of domains. 20-40: 32.7% of domains. 40-60: 60.7% of domains. 60-80: 6.2% of domains. 80-100: 0.4% of domains.

Each square is about 1% of all 761,367 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.

To place a single domain on that curve, compare it against the same corpus with the scanner that produced these numbers.

How we measured this

Each of the 761,367 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:

CriticalAn open hole an attacker can use right now, such as running code on the page or taking over a session. Fix immediately.
HighA serious gap that exposes credentials or data, or lets an attacker escalate. Fix urgently.
MediumA real weakness that thins the defenses against one specific attack. Worth fixing soon.
LowA minor issue with narrow or indirect impact. Tidy up during normal maintenance.
InfoA quality nudge with no direct security impact. These shape the Quality score, never the Security one.

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 19.4% over a larger and broader population. Two independent crawls landing within two and a half points of each other 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. Three of the four vectors leave more than 97% of sites exposed.

Full protection is a rounding error. Just 108 domains pass on script injection and 870 on data exfiltration; supply-chain tampering is stopped on a single domain in the entire corpus.

The one exception is clickjacking, where 7.1% pass and another third hold a partial defense, and only because the fix is one old, simple header. Even there, three sites in five 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 7.1%Partial 32.5%Exposed 60.5%
Corpus exposure
Protected53,852 (7.1%)
Partial247,265 (32.5%)
Exposed460,250 (60.5%)
Average resistance 35/100

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 14 passes (7.1%), and a further third carries some defense (32.5%). The other 60.5% 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 35 out of 100 in resistance.

Corpus exposure

Protected 0.0%Partial 0.7%Exposed 99.3%
Corpus exposure
Protected108 (0.0%)
Partial5,135 (0.7%)
Exposed756,124 (99.3%)
Average resistance 3/100

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 761,367 domains, 108 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.

Corpus exposure

Protected 0.0%Partial 2.3%Exposed 97.7%
Corpus exposure
Protected1 (0.0%)
Partial17,579 (2.3%)
Exposed743,787 (97.7%)
Average resistance 4/100

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, a further 2.4% hold a partial defense, and the average resistance is 4 out of 100 against 97.7% exposed.

A single compromised dependency would run unchecked almost everywhere.

Corpus exposure

Protected 0.1%Partial 1.3%Exposed 98.6%
Corpus exposure
Protected870 (0.1%)
Partial9,673 (1.3%)
Exposed750,824 (98.6%)
Average resistance 50/100

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.

870 of 761,367 domains pass and 98.6% are exposed. The partial defenses come from the 1.3% of sites that pin some channels but not all.

This vector carries the highest average resistance of the four, 50 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 66.2%, 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 39.9%, X-Frame-Options at 37.3%, Strict-Transport-Security at 34.0%. The cross-origin headers, which take real thought, fall to 1.2% 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 on 98.6%. Cross-Origin-Resource-Policy is the exception: only 57.6% of its senders pass. More than four in ten set it to cross-origin, which grants exactly what sending no header at all grants, so the header is present and the protection is not.

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 86.7% of its senders, Cache-Control on 86.8%.

One header breaks the pattern. A third of all sites send Strict-Transport-Security, but barely one sender in five sends it correctly: 22.1% of HSTS senders pass, while the rest ship a short max-age or skip includeSubDomains and preload. A further 8,380 sites send it with max-age=0, which switches the protection off outright.

That gap is the whole story of this section. Counted against the entire corpus, fully correct HSTS reaches just one site in 13 (7.5%). 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.

The same checks run on demand: test the headers your own site sends.

Protective response headers

Share of all 761,367 scored domains that send each header, and how the senders score.

HeaderAdoptionPassPartialFailed
Cache-Control
66.2%
86.8 / 0.0 / 13.2
X-Content-Type-Options
39.9%
99.9 / 0.0 / 0.1
X-Frame-Options
37.3%
98.6 / 1.4 / 0.0
Strict-Transport-Security
34.0%
22.1 / 52.9 / 25.0
Referrer-Policy
26.4%
86.7 / 0.0 / 13.3
Cross-Origin-Resource-Policy
1.2%
57.6 / 42.4 / 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.

HeaderWhy it was retiredStill sent by
X-XSS-ProtectionDrove the XSS auditor every browser has since removed; only 0 is safe.
18.6%
Expect-CTCertificate Transparency reporting, redundant since CT became mandatory.
1.0%
Feature-PolicyRenamed and replaced by Permissions-Policy.
0.7%
Public-Key-PinsOne wrong pin could lock users out of the site; replaced by Certificate Transparency.
0.0%

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 nearly a fifth of the web still sends the header (18.6%, 141,522 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.0%, 128,730 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.

18.6%

still send X-XSS-Protection

141,522 of 761,367 domains keep a header every current browser has removed.

91.0%

of those use the unsafe value

128,730 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.4%), and over half of those print the version too (56.1% of the disclosers), leaving about one site in five exposing the exact release it runs (22.1% 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.

39.4%

name their technology in a header

300,232 domains advertise what they run through Server, X-Powered-By and similar headers.

22.1%

also expose the exact version

168,527 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 672,987 cookies in the corpus, about half carry each protective flag: Secure on 54.3%, HttpOnly on 49.6%, SameSite on 45.5%. Fewer than half declare SameSite at all.

The SameSite value is the sharper twist. Of the cookies that set it, Lax is the plurality at 48.8%, but None, the value that opts back out of cross-site protection, sits right behind it at 45.7%, and Strict is a rounding error at 5.4%.

So the hardened-cookie choice is close to a coin flip between the safe default and the opt-out. Counted against every cookie in the corpus, None still lands on about one in five (20.8%), while Strict stays negligible.

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 672,987 cookies in the corpus that set each attribute.

AttributeWhat it doesAdoptionCookies
SecureOnly sent over HTTPS, never on a plain connection.
54.3%
365,248
HttpOnlyHidden from JavaScript, so an injected script cannot read it.
49.6%
333,599
SameSiteLimits when the cookie rides along on cross-site requests.
45.5%
305,897

SameSite value, when set

None 45.8%Lax 48.8%Strict 5.4%
SameSite value, when set
None139,940 (45.8%)
Lax149,181 (48.8%)
Strict16,612 (5.4%)

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 one site in five sends 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 19.4%No CSP 80.6%
Adoption
Ships a CSP148,000 (19.4%)
No CSP613,367 (80.6%)

Security outcome

Pass 1.5%Partial 7.1%Failed 91.4%
Security outcome
Pass2,251 (1.5%)
Partial10,446 (7.1%)
Failed135,303 (91.4%)

Delivery method

Header 96.0%Both 1.2%Meta tag 2.7%
Delivery method
Header146,113 (96.0%)
Both1,887 (1.2%)
Meta tag4,164 (2.7%)

Mode

Enforced 89.5%Both 2.7%Report-only 7.8%
Mode
Enforced132,467 (89.5%)
Both3,977 (2.7%)
Report-only11,556 (7.8%)

How many domains ship a Content-Security-Policy, how those policies score, and how they are delivered and enforced.

Adoption is the easy half. 19.4% of the web ships a CSP (148,000 of 761,367 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.4% fail (135,303 sites) and only 1.5% pass (2,251 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 (89.5%), and 96.0% arrive in a header, with only 2.7% relying on a meta tag alone. The header is present; the protection is not.

At corpus scale, a passing CSP exists on 0.29% of all domains. Fewer than three sites in a thousand have the control that would contain the most common client-side attack.

The gap between authoring quality and security is what a policy written by hand looks like. Building one from real browser reports closes it, because the directives come from what your visitors actually load.

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.9% of CSP sites and passes 76.9% of the time, because it does one narrow job. script-src, the directive that actually gates injected scripts, appears on 40.5% of CSP sites and fails 89.1% of the time (45,390 of 50,916 uses).

That pattern holds down the table. The directives that constrain scripts and styles fail hardest (style-src passes 9.3% of the time, script-src-elem 2.7%), while the structural ones pass easily: base-uri passes 91.6%, 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 285 CSP sites, trusted-types on 166.

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 152,164 CSP-delivering domains that set each directive, and how those uses score.

DirectiveUsedPassPartialFailed
frame-ancestors
54.9%
76.9 / 13.5 / 9.6
default-src
41.8%
53.0 / 2.4 / 44.6
script-src
40.5%
6.4 / 4.4 / 89.1
img-src
37.9%
17.2 / 3.0 / 79.8
style-src
34.8%
9.3 / 49.6 / 41.2
font-src
34.4%
71.6 / 4.8 / 23.6
connect-src
34.2%
17.7 / 1.1 / 81.1
upgrade-insecure-requests
33.2%
100.0 / 0.0 / 0.0
frame-src
30.6%
26.4 / 15.1 / 58.5
object-src
28.4%
76.5 / 0.0 / 23.5
base-uri
26.3%
91.6 / 0.0 / 8.4
form-action
19.4%
68.1 / 5.0 / 27.0
media-src
17.8%
64.2 / 4.1 / 31.7
worker-src
14.9%
63.0 / 1.0 / 36.0
child-src
8.2%
0.0 / 49.2 / 50.8
report-uri
7.8%
99.1 / 0.8 / 0.1
manifest-src
7.3%
74.3 / 18.8 / 6.8
block-all-mixed-content
4.2%
0.0 / 100.0 / 0.0
script-src-elem
3.3%
2.7 / 4.7 / 92.6
report-to
3.0%
99.6 / 0.2 / 0.2
style-src-elem
2.1%
10.6 / 45.2 / 44.3
script-src-attr
2.0%
48.0 / 2.4 / 49.7
style-src-attr
1.3%
7.7 / 87.1 / 5.1
require-trusted-types-for
0.2%
99.3 / 0.7 / 0.0
prefetch-src
0.1%
0.0 / 80.7 / 19.3
sandbox
0.1%
4.1 / 2.6 / 93.4
trusted-types
0.1%
30.1 / 62.0 / 7.8
fenced-frame-src
0.0%
0.0 / 70.0 / 30.0
navigate-to
0.0%
0.0 / 96.4 / 3.6
plugin-types
0.0%
0.0 / 100.0 / 0.0
referrer
0.0%
0.0 / 100.0 / 0.0
reflected-xss
0.0%
0.0 / 100.0 / 0.0
require-sri-for
0.0%
0.0 / 100.0 / 0.0
webrtc
0.0%
0.0 / 100.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 38.1% of CSP sites and unsafe-eval on 33.0%. Nonces, the headline hardening mechanism, reach 5.3%. 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.5% of CSP sites (3,853 policies). Trusted Types, the strongest DOM-injection defense the platform offers, is enforced on 348.

The weakening list also runs deeper than the two famous keywords. More than one policy in five allows a known JSONP endpoint (22.1%) or a known script gadget (21.2%), 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 152,164 CSP-delivering domains.

Hardening mechanismDomainsHardening
upgrade-insecure-requests50.5k
33.2%
Nonces8k
5.3%
strict-dynamic3.9k
2.5%
Hashes2.9k
1.9%
Trusted Types (enforced)348
0.2%
Trusted Types (declared)203
0.1%

The mechanisms that strengthen a policy, each as a share of CSP-delivering domains.

Weakening mechanism

Share of the 152,164 CSP-delivering domains.

Weakening mechanismDomainsWeakening
unsafe-inline58k
38.1%
unsafe-eval50.3k
33.0%
JSONP endpoint33.6k
22.1%
Script gadget32.2k
21.2%
Subdomain wildcard28.1k
18.4%
Scheme source27.3k
17.9%
Shared host17.7k
11.7%
Full wildcard *5.6k
3.7%
wasm-unsafe-eval1.9k
1.2%
unsafe-hashes842
0.6%

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 8.1% of CSP sites, and it is a source value listed twice in the same directive.

Below it the list stays in single digits: a keyword used in a directive that ignores it (6.2%), a source already covered by a broader one (6.1%), a deprecated directive copied from an old template (4.3%). These are hygiene problems, not holes.

The ones with real teeth are rare, which is exactly why they survive. About one CSP site in 89 writes a keyword without its quotes (1.1%), so unsafe-inline or self is parsed as a hostname and the directive quietly means something the author never wrote.

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 152,164 CSP-delivering domains hit by each mistake.

Authoring mistakeDomainsShare of CSP sites
Duplicate value in a directive12.3k
8.1%
Keyword not valid in that directive9.4k
6.2%
Source already covered by a broader one9.2k
6.1%
Deprecated directive6.5k
4.3%
Source value with no effect3.9k
2.6%
Multiple CSP headers3.7k
2.5%
Directive allows localhost2.9k
1.9%
Policy sent in both a meta tag and a header1.9k
1.2%
Keyword missing its quotes1.7k
1.1%
Unknown directive1.2k
0.8%

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 33.7% of domains, ahead of Content-Security-Policy at 19.4%, and it gets there because CDNs switch it on rather than because operators chose it.

Below CSP the modern headers thin out fast. COOP reaches 9.8% and Permissions-Policy 9.1%, roughly one site in eleven. COEP, the second half of cross-origin isolation, lands on 0.6% 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 seventeen, so almost every COOP deployment cannot reach isolation at all.

The genuinely new policies barely register. Document-Policy sits at 0.1% (720 domains), while Integrity-Policy and Connection-Allowlist are a literal handful, 16 domains and 6. 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 761,367 scored domains that deploy each policy.

PolicyDeployedEnforcedBothReport-only
Network Error Logging
33.7%
100.0 / 0.0 / 0.0
Content-Security-Policy
19.4%
89.5 / 2.7 / 7.8
Cross-Origin-Opener-Policy
9.8%
98.7 / 1.1 / 0.2
Permissions-Policy
9.2%
100.0 / 0.0 / 0.0
Cross-Origin-Embedder-Policy
0.6%
76.8 / 19.2 / 4.0
Document-Policy
0.1%
99.4 / 0.1 / 0.4
Connection-Allowlist
0.0%
20.0 / 0.0 / 80.0
Integrity-Policy
0.0%
31.3 / 0.0 / 68.8

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.4% of the policies that get evaluated and passes on 1.5%. Every other widely deployed policy passes almost every time it is set: Permissions-Policy at 100%, NEL at 100%, COOP at 90.9%.

COEP is the interesting middle case. It passes on only 19.9% of its deployers, with the other 80.1% 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.

PassPartialFailed
Policy health
PolicyPassPartialFailed
CSP1.5%7.1%91.4%
NEL100%0%0%
COOP90.9%9.1%0%
Permissions100%0%0%
COEP19.9%80.1%0%
Document98.6%1.4%0%
Connection100%0%0%
Integrity40%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 256,658 domains; every other report type a site would actually choose adds up to 19,907 domains between them.

The report that matters most is the rarest of the common ones. 1.9% of the corpus collects its own Content-Security-Policy violations: 14,197 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,112 domains that wire up COOP reporting, 81.3% have it broken, and COEP reporting is broken on 92.4% of the 1,005 that try.

CSP reporting is in better shape: 97.5% 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.5% working for CSP and 7.6% 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 761,367 domains; working and broken split each row's own set-up count.

Report typeSet up to reportWorkingBroken
Network error
33.7%
100.0 / 0.0
CSP violation
1.9%
97.5 / 2.5
COOP report
0.1%
18.7 / 81.3
Crash
0.1%
96.8 / 3.2
Deprecation
0.1%
96.8 / 3.2
Intervention
0.1%
96.8 / 3.2
COEP violation
0.1%
7.6 / 92.4
Permissions-Policy violation
0.1%
99.2 / 0.8
CSP script-hash report
0.0%
95.2 / 4.8
Integrity violation
0.0%
92.3 / 7.7
Document-Policy violation
0.0%
77.8 / 22.2
Connection-Allowlist
0.0%
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, 98.1% still use the deprecated Report-To, and 1.9% have moved to the modern header.

That deprecated slice is the same CDN plumbing already counted above. Report-To sits on 34.1% of the corpus, near enough 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: 5,065 sites, 0.7% of the corpus, roughly one site in 151.

Wiring it up is one response header, and collecting what browsers send back is where the numbers in this section come from.

Domains declaring a reporting header

Reporting-Endpoints 1.9%Report-To (deprecated) 98.1%
Domains declaring a reporting header
Reporting-Endpoints5,065 (1.9%)
Report-To (deprecated)259,388 (98.1%)

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?

157 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.6%) 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 site in seven leaves connect-src open enough to exfiltrate freely (15.1%), and 7.4% still allow unsafe-inline in script-src.

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 21 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 157 findings in the catalog, weighted by severity and reach, over all 761,367 domains.

PriorityRecommendationSeverityDomainsShare of corpus
Do nowAdd a strict nonce-based script-src with 'strict-dynamic'.High755.1k99.2%
Do nowAdd an Integrity-Policy header to require SRI for scripts.High752.9k98.9%
Do nowSet connect-src to 'self' and your known hosts.High750.7k98.6%
Fix your CSPRestrict connect-src to 'self' and your API/analytics hosts.High114.6k15.1%
Fix your CSPDrop 'unsafe-inline'; use a nonce with 'strict-dynamic'.High56.6k7.4%
Fix your CSPRemove 'unsafe-eval' from the directive.High50.3k6.6%
Fix your CSPRemove the dangerous scheme from the directive.High33.8k4.4%
Fix your CSPRemove the JSONP host from the directive.High32.8k4.3%
Fix your CSPRemove the script gadget host from the directive.High31.6k4.1%
Fix your CSPReplace the shared host with a dedicated one you control.High17.6k2.3%
Fix your CSPMove the CSP to the enforced header.High11.5k1.5%
Fix your CSPRemove the scheme source from the directive.High10.9k1.4%
Do nowReplace max-age=0 with max-age=31536000.High8.4k1.1%
Fix your CSPReplace default-src * with 'self'/'none' and set per-directive sources.High4.7k0.6%
Fix your CSPReplace the wildcard in the directive with a nonce policy.High3.5k0.5%
Fix your CSPGenerate a fresh nonce for every response.High1.4k0.2%
RecommendedAdd a Permissions-Policy header disabling unused features.Medium691.2k90.8%
Fix your CSPAdd a strict nonce-based Content-Security-Policy.Medium609.2k80.0%
RecommendedAdd a NEL header with a report_to group declared in Report-To.Medium504.7k66.3%
RecommendedDeclare a Reporting-Endpoints group and reference it.Medium499.7k65.6%

157 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 92.4 out of 100; the whole corpus of 761,367 averages 46.0. That is a 46.4-point gap, as large again as the typical score.

Split that gap by axis and it turns out to be one axis. On quality the two populations are almost indistinguishable: 97.5 for the best cohort against 96.6 for everyone else, less than a point apart.

On security they are not the same web. The best cohort averages 90.9 out of 100 against the corpus average of 29.2, a chasm of 61.5 points. The entire distance between the two populations sits on that one axis.

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.

Best cohort average92.4/100
Corpus average46.0/100
The gap+46.4pts

The best-configured 150 domains against all 761,367, 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 761,367 domains, every spoke a 0-100 sub-score.

The two series are not the same size. Green is fewer than 1 site in 5,000. 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 7, a fourteenfold split and the widest of all. Attack resistance runs 88 against 23, and the policy headers 61 against 15, roughly four times the average in both cases.

Two areas read against that grain. Security headers is the one place the average clears half, at 54 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 8 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 rather than badly configured.

Best cohortCorpus average

Security Headers

Security Headers
Security HeadersBest cohortCorpus averageDomains measured
X-Content-Type-Options100/10040/100761,367
Referrer-Policy100/10024/100761,367
Cache-Control99/10092/100505,415
Strict-Transport-Security99/10023/100750,390
Cross-Origin-Resource-Policy98/10083/1009,067

Attack Resistance

Attack Resistance
Attack ResistanceBest cohortCorpus averageDomains measured
Clickjacking99/10035/100761,367
Data Exfiltration99/10050/100761,367
Script Injection82/1003/100761,367
Supply Chain70/1004/100761,367

Reporting-API Policies

Reporting-API Policies
Reporting-API PoliciesBest cohortCorpus averageDomains measured
Integrity-Policy100/10082/1005
Permissions-Policy96/1009/100761,346
Document-Policy94/100100/100717
Cross-Origin-Opener-Policy71/10010/100761,213
Content-Security-Policy61/10047/100148,000
Cross-Origin-Embedder-Policy45/1000/100761,206
Network Error Logging30/10034/100761,367
Connection-Allowlist0/100100/1001

Reporting-API Setup

Reporting-API Setup
Reporting-API SetupBest cohortCorpus averageDomains measured
CSP violation36/1002/100761,367
Network error28/10034/100761,367
Crash6/1000/100761,367
Deprecation6/1000/100761,367
Intervention6/1000/100761,367
Permissions-Policy violation5/1000/100761,367
COOP report4/1000/100761,367
COEP violation3/1000/100761,367
Connection-Allowlist1/1000/100761,367
CSP script-hash report1/1000/100761,367
Document-Policy violation1/1000/100761,367
Integrity violation1/1000/100761,367

Content Security Policy

Experimental directiveDeprecated directive
Content Security Policy
Content Security PolicyBest cohortCorpus averageDomains measured
base-uri100/10020/100146,602
default-src100/10025/100143,920
font-src100/10027/100132,731
form-action100/10012/100147,670
frame-ancestors100/10087/10076,310
frame-src100/10017/100130,194
manifest-src100/1007/10095,459
media-src100/10015/100112,749
report-to100/10099/1003,088
report-uri100/100100/1008,478
require-trusted-types-for100/100100/100287
script-src100/10010/100142,389
style-src-elem100/10046/1003,188
upgrade-insecure-requests100/100100/10049,894
worker-src100/10016/100101,932
trusted-types99/10091/100167
img-src98/10016/100148,508
connect-src97/10013/100144,155
object-src96/10022/100147,729
block-all-mixed-content94/10094/1006,137
script-src-attr94/10063/1002,887
webrtc94/10094/10045
style-src90/10016/100138,004
child-src88/1004/10099,913
style-src-attr82/10055/1002,008
script-src-elem81/10019/1004,915
sandbox71/10046/100201
fenced-frame-src0/10064/10021
navigate-to0/10092/10028
plugin-types0/10094/10019
prefetch-src0/10077/100123
referrer0/10094/10015
reflected-xss0/10094/10016
require-sri-for0/10054/10014

Each radar plots every posture control we measure, hygiene headers aside, however few domains use it. Read both extremes with care. A spoke near zero can mean the control is configured badly, or simply that almost nobody sets it. A spoke near the rim can mean the same: each average covers only the domains that set that control, so a rarely used one often sits high because the handful who bother also configure it well. Green is 150 domains, amber is all 761,367.

The best sites settle the report's central question. A strict, secure posture is fully buildable, and a 1-in-5,000 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.