← All insights

The Security Header 96% of Sites Still Don’t Set

The Security Header 96% of Sites Still Don’t Set

Every page a browser loads can, by default, be asked to hand a visitor’s camera, microphone, or location to any script running on it, including scripts your site didn’t write and doesn’t control. Almost nobody explicitly closes that door.

What Permissions-Policy actually controls

Permissions-Policy is a response header that declares which browser features and APIs a page, and anything embedded in it, is allowed to use. Camera, microphone, geolocation, fullscreen, autoplay, USB access, and dozens of other capabilities can each be allowed, restricted to your own origin, or blocked outright, for the page itself and separately for any third-party iframe it embeds.

Without it, the browser’s defaults apply, and those defaults are generally permissive enough that an embedded ad, widget, or compromised dependency can attempt to use capabilities the page hosting it never intended to expose.

A policy is a single header listing each feature and who it’s allowed for:

Permissions-Policy: camera=(), microphone=(), geolocation=(self), fullscreen=(self "https://embed.example.com")

That example blocks camera and microphone entirely for anyone, restricts geolocation to the site’s own origin, and allows fullscreen for the site itself plus one specifically named embedded origin. Everything not mentioned falls back to the browser’s default, which is why an explicit policy matters even more for the features a site doesn’t think to list.

The header almost nobody sets

Per the Web Almanac, HTTP Archive’s annual survey of the real web, Permissions-Policy appears on roughly 3.7% of sites, even after growing about 60% relative to the year before. That’s well behind every other major security header: Content-Security-Policy sits at 21.9%, HSTS at 36%, and even X-Content-Type-Options, the most commonly set of the group, only reaches around half of sites. Permissions-Policy isn’t just under-adopted, it’s the least-adopted header of the bunch by a wide margin, despite costing nothing more than the header itself to add.

That’s a similar shape to what we found writing about CAA records: a control that costs almost nothing to set, closes off a real gap, and still sits unused on the overwhelming majority of sites simply because almost nobody knows to look for it.

Why this matters even if you don’t use any of these features

The instinctive response is that a header restricting camera and microphone access doesn’t matter for a site that never asks for either. That’s backwards. If a page genuinely never needs a feature, explicitly blocking it costs nothing and removes an entire attack surface for anything that shouldn’t have had access to it in the first place, a compromised third-party script, a misconfigured ad tag, an XSS payload that got further than it should have.

Third-party embeds are the sharper version of the same problem. An iframe from an ad network or a widget provider can inherit or request permissions the hosting page never explicitly considered, unless Permissions-Policy is used to restrict what embedded content is allowed to do, separately from what the top-level page can do. Most sites embed at least one third-party script without ever asking what it’s technically permitted to reach for.

Ad tech is the most common real-world case, but it isn’t the only one. A support chat widget, an analytics snippet, an embedded video player, a payment iframe, each is a piece of code a site didn’t write, running with whatever the browser’s defaults allow unless something says otherwise. The policy doesn’t need to anticipate every possible misuse. It just needs to state, explicitly, what’s actually needed, and let the browser refuse everything else by default rather than allow it by default.

Does setting the header alone control what an embedded iframe can do?

Not entirely. The header sets the ceiling for the whole document, but an individual iframe also needs its own allow attribute to actually use a feature. Both have to agree: the header must include the iframe’s origin in its allowlist, and the iframe element itself needs allow="camera" or the equivalent for that specific feature. Set the header without touching the markup, and an embed that needs a feature can still be blocked by the missing attribute. Add the attribute without a permissive header, and the header blocks it regardless of what the markup says. They’re two separate locks, not one.

A reasonable starting point for a site with no legitimate use for hardware or payment APIs, which describes most content and marketing sites, blocks the highest-risk features outright and leaves everything else to be added deliberately as it’s actually needed:

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()

A quick self-audit

  • Check whether your site sends a Permissions-Policy header at all. At current adoption rates, there’s a reasonable chance it doesn’t.
  • Explicitly block features your site has no legitimate use for, rather than leaving the browser’s permissive defaults in place.
  • Set separate, tighter restrictions for third-party iframes than for your own origin, particularly for ad networks, embeds, and widgets you don’t control the code of.
  • Revisit the policy whenever a new third-party script or embed gets added. It’s an easy thing to set once and never update.

kant.au checks your site’s security headers, including Permissions-Policy, alongside SSL, DNS, and email authentication for every domain you monitor. Want to see what your site is currently sending? Check it for free.


HTTP Archive: Web Almanac 2025, Security
MDN: Permissions-Policy header