Security Headers

We scanned 5 popular security scanners — and every one still leaked something

The test

We were curious. If a product sells website security scanning, how does its own site hold up? So we took five popular automated security scanners — the kind aimed at people shipping apps with AI tools — and ran each one through our own engine.

No names here. This is not a hit piece, and most of these teams are doing good work. We are interested in one question only: what does a security-conscious website still leak in 2026? We ran each one through our full rule set and read what came back.

What we found: all Medium, none Critical

Nobody had an exposed secret. Nobody had a broken certificate or a downloadable .env. Across all five, the findings clustered into three small, boring, easy-to-miss buckets:

1. A permissive Content-Security-Policy. Nearly every site allowed inline scripts via unsafe-inline in script-src. This is the single most common real-world CSP finding, because React, Next.js and Vite all lean on inline scripts by default. The catch: unsafe-inline quietly defeats CSP against the most common XSS payloads — anything an attacker can inject into the HTML simply executes. A CSP with unsafe-inline looks like protection and mostly is not.

2. Missing modern security headers. Several sites were missing Cross-Origin-Opener-Policy, Permissions-Policy, or Referrer-Policy. Individually these are Low or Info severity — but they are free wins that ship in one line each, and their absence is a reliable tell that the header config has not been revisited in a while.

3. An internal URL leaked into production. One site served content that referenced an internal-only host — a localhost, a private 10.x IP, or a .staging/.internal hostname. These are leftover debug code or build-time constants that escaped into the bundle. Even when the host is unreachable from the public internet, it leaks architecture detail — internal service names, ports, API shapes — that an attacker uses to aim.

Why none of this is a scandal

Mediums, not criticals. This is normal hygiene drift, not negligence. Frameworks reintroduce unsafe-inline on the next upgrade. Build pipelines bake in a constant someone forgot to strip. New headers land in the spec faster than anyone updates their config. It happens to good teams — it happened to five of them at once.

The real lesson: the small stuff is invisible without a scanner

You cannot eyeball a missing Permissions-Policy. You cannot spot an internal URL buried in a two-megabyte minified JavaScript bundle. You will not notice that last week's framework bump re-enabled unsafe-inline. These are exactly the findings a one-time manual check misses — and exactly what a thorough, repeated scan catches. That is the whole argument for continuous scanning: not the dramatic breach, but the slow, invisible drift.

How to fix the four we saw

  • CSP unsafe-inline: move to nonce- or hash-based script-src and drop unsafe-inline. Roll it out in report-only mode first so you see violations before you enforce.
  • Cross-Origin-Opener-Policy: add Cross-Origin-Opener-Policy: same-origin.
  • Permissions-Policy: turn off the browser features you do not use, e.g. Permissions-Policy: camera=(), microphone=(), geolocation=().
  • Referrer-Policy: set Referrer-Policy: strict-origin-when-cross-origin.
  • Internal URLs: strip localhost, private-IP and .staging/.internal references in your build step, not by hand.

Scan your own site

The interesting part of this experiment was not catching anyone out — it was the reminder that clean and perfect are different things. Run your own site and see which of these four it still leaks.

Related reading

FAQ

Does unsafe-inline in my CSP really matter if I have no known XSS?
Yes. CSP is defense-in-depth — it is what protects you on the day a dependency or a form does introduce an injection. With unsafe-inline, that protection is off for the most common payloads, so the CSP gives a false sense of safety.
Are missing headers like Permissions-Policy actually exploitable?
Rarely on their own — they are Low or Info severity. But they are one-line wins that shrink your attack surface, and a missing one usually signals the whole header config is overdue for a review.
Why scan continuously instead of once before launch?
Because the small findings come back. A framework upgrade re-enables unsafe-inline, a build leaks a new internal URL, a header config drifts. A one-time scan is a snapshot; the risk is the change you did not notice.