Permissions Policy Header – Controlling Browser APIs

Permissions Policy Header – Controlling Browser APIs

Permissions Policy Header controls which browser features and APIs a website – and any embedded third-party content – is allowed to use, from camera and microphone access to geolocation, autoplay, and USB device pairing. For anyone auditing security headers as part of a broader review, this one often gets skipped because it doesn’t stop an attack the way CSP or HSTS does – but it closes a different kind of gap, and that gap gets exploited more often than most teams realize.

What the Permissions Policy header actually does

Browsers expose dozens of powerful APIs to web pages – camera, microphone, geolocation, fullscreen, payment, USB, gyroscope, autoplay, and more. By default, many of these are available not just to your own scripts but to any iframe you embed, including third-party widgets, ad tenants, and analytics tags.

The Permissions Policy header lets a server declare, explicitly, which of these APIs are allowed and where. It replaced the older Feature-Policy header (still seen in some older codebases) and uses a simpler allowlist syntax per feature.

A basic example:

Permissions-Policy: geolocation=(), camera=(), microphone=(self)

This disables geolocation and camera entirely, and restricts microphone access to your own origin only – no embedded iframe, however trusted it looks today, gets a free pass.

Why this header matters more than it used to

A few years ago, this header was mostly theoretical – few sites embedded content that needed camera or location access, so there wasn’t much to restrict. That’s changed. Chat widgets request microphone access for voice messages, embedded maps ask for geolocation, payment iframes touch the Payment Request API, and video players trigger autoplay and fullscreen.

Every one of those is a third-party script running in your page’s context. If that vendor gets compromised – through a supply chain attack on their build pipeline, for instance – and starts requesting camera or microphone permissions silently, a Permissions Policy restriction is often the only thing standing between “vendor had a bad day” and “our visitors got recorded without knowing it.” This is the same threat class covered in Supply Chain Attacks Through Compromised Dependencies – the header doesn’t prevent the compromise, but it limits what a compromised script can actually do once it’s running.

Common mistake: treating it as a copy-paste header

The most frequent misconception is that Permissions Policy is a “set it once, forget it” header, similar in spirit to X-Content-Type-Options. It isn’t. Every feature directive needs deliberate thought about what your site and its embedded content genuinely require.

A common failure pattern looks like this: a team copies a hardened Permissions Policy from a blog post, deploys it, and two days later support tickets start coming in because the embedded video player stopped working – the copied policy disabled autoplay and fullscreen globally, features the video vendor actually needed. The fix isn’t to abandon the header; it’s to audit which APIs your page and its iframes genuinely use before writing the policy, then test in staging with every embedded widget active.

How to build a Permissions Policy that won’t break things

1. Inventory every third-party script and iframe on the page – widgets, payment providers, maps, video, chat, analytics.
2. For each, check documentation or network behavior to see which browser APIs it requests.
3. Start restrictive by default: disable features you don’t use (camera, microphone, geolocation, usb, midi, payment) with empty allowlists.
4. For features you do need, scope them narrowly – self only, or a named origin, rather than a wildcard.
5. Test with the actual embedded content live, not a stripped-down staging page, since permission failures often only surface when the third-party script actually tries to call the API.
6. Re-check the policy whenever a new embed or widget is added – this is the step most teams skip, and it’s where drift creeps in silently.

Permissions Policy vs. other security headers

It’s easy to confuse this header with Content-Security-Policy, since both use allowlist-style syntax and both live in HTTP response headers. They solve different problems, though. CSP controls where scripts, styles, and other resources can load from – it’s about content sourcing, covered in more depth in How to Implement Content Security Policy (CSP) Correctly. Permissions Policy controls what browser APIs loaded content is allowed to invoke, regardless of where it came from.

Feature-Policy, the header’s predecessor, still shows up in some legacy configurations and older documentation. It works similarly but uses a slightly different syntax and has been deprecated in modern browsers in favor of Permissions-Policy. If an automated scan flags Feature-Policy as present without Permissions-Policy, that’s a sign the header configuration hasn’t been updated in a while – worth checking alongside the rest of the header stack, since headers tend to get configured once at launch and rarely revisited afterward, a pattern discussed in Security Headers Explained: CSP, HSTS, and X-Frame-Options.

Busting the myth: “no embedded content means no risk”

A common assumption is that sites without obvious third-party widgets don’t need this header at all. That overlooks two things. First, ad networks, analytics pixels, and font or script CDNs frequently load additional scripts dynamically, which can change what runs on your page without a corresponding code deploy on your end. Second, a Permissions Policy also restricts what your own first-party JavaScript can do if it’s ever compromised through an XSS flaw – it’s not purely a third-party control. A page with no iframes at all can still benefit from disabling camera, microphone, and USB access it never uses, simply as defense in depth against an attacker who manages to inject a script.

FAQ

Does Permissions Policy stop XSS or SQL injection attacks?
No. It restricts which browser APIs a page and its embedded content can access – it doesn’t prevent script injection or database attacks. It works alongside controls like CSP and input validation, not as a replacement for them.

Will adding this header break existing third-party integrations?
It can, if the policy is too restrictive for what an embedded vendor’s script actually needs. That’s why testing with live embeds in staging before production rollout matters more for this header than for most others.

How do I know which directives my site actually needs?
Start by auditing every embedded script and iframe, then check browser developer tools for permission API calls while the page runs normally. A site security audit that inspects response headers alongside actual page behavior will usually surface gaps faster than manual review alone.

Getting Permissions Policy right is less about memorizing directive names and more about knowing exactly what your page and everything embedded in it is allowed to touch. Review it every time a new widget, ad tag, or embed gets added – that’s the point where policies quietly go stale and coverage gaps open up unnoticed.