Privacy & Security

What the Permissions-Policy header can actually lock down

A practical guide to the browser features you can restrict, what you cannot control, and how to deploy the header without breaking useful functionality.

The Wux Webtools Team The Wux Webtools Team 8 min read AI-assisted, human-reviewed
A browser interface with feature toggles limiting access for a page and embedded frames.
Table of contents
  1. The short version
  2. What Permissions-Policy controls
  3. What it can lock down on your own pages
  4. What it can lock down in iframes
  5. What it cannot lock down
  6. A sensible default policy
  7. How to deploy without breaking things
  8. 1. Inventory feature use
  9. 2. Start in a low-risk environment
  10. 3. Use page-specific policies where necessary
  11. 4. Verify the real response
  12. 5. Document exceptions
  13. Common syntax mistakes
  14. The practical privacy value

The short version

Permissions-Policy is an HTTP response header that lets a site limit access to certain browser features: camera, microphone, geolocation, fullscreen, payment, sensors, and a long list of smaller APIs.

It is not a general privacy shield. It will not stop all tracking, block cookies, prevent network requests, or make third-party JavaScript safe. What it can do well is narrower and still valuable: reduce the browser capabilities available to your own pages and to embedded frames.

That matters because modern websites are stitched together from analytics snippets, media embeds, chat widgets, consent managers, advertising scripts, maps, payment flows, and internal experiments. Most of those components do not need access to powerful device APIs. A good policy makes that explicit.

If you are already reviewing headers in production, pair this work with a direct check of the actual response. Our guide to debugging redirects and HTTP headers in production covers the habit that matters here: inspect what the browser really receives, not what your configuration file says should happen.

What Permissions-Policy controls

The header controls access to named browser features. The exact list changes over time because browser APIs change, but common directives include:

  • camera
  • microphone
  • geolocation
  • fullscreen
  • payment
  • usb
  • bluetooth
  • accelerometer
  • gyroscope
  • magnetometer
  • screen-wake-lock
  • clipboard-read
  • clipboard-write
  • publickey-credentials-get
  • interest-cohort in older discussions, now mostly historical

A directive can allow a feature for no one, for the current origin, or for selected origins. For example:

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

This means camera and microphone are disabled for the document and its nested browsing contexts, while geolocation is allowed for the same origin only.

A more permissive example:

Permissions-Policy: geolocation=(self "https://maps.example"), fullscreen=(self "https://video.example")

This allows your own origin and one named map provider to use geolocation, and your own origin plus a video provider to request fullscreen.

The policy is evaluated by the browser. If a feature is disallowed, JavaScript using that API should fail or behave as unavailable. The exact failure mode depends on the API. Sometimes a promise rejects. Sometimes a capability simply does not appear usable.

What it can lock down on your own pages

On first-party pages, Permissions-Policy is most useful as a guardrail. It reduces the blast radius of accidental or unexpected code.

A marketing page, for example, probably does not need the microphone, camera, Bluetooth, USB, motion sensors, or payment APIs. You can deny those features globally:

Permissions-Policy: camera=(), microphone=(), bluetooth=(), usb=(), payment=(), accelerometer=(), gyroscope=(), magnetometer=()

That does not make every script on the page trustworthy. It does mean that if a tag manager experiment, compromised dependency, or pasted widget tries to call a restricted API, the browser should not grant it access.

For teams with many contributors, this is a useful default. It moves the conversation from vague trust to explicit capability. If a future feature genuinely needs the camera, someone has to change the policy and explain why.

That is the right kind of friction.

What it can lock down in iframes

The header becomes especially useful around embedded content.

Browsers already treat iframes as separate browsing contexts, but embedded third-party content can still request powerful features if allowed by policy and iframe attributes. Permissions-Policy lets the parent page set a ceiling.

For example, if your page embeds a video player, a support widget, and a map, you can avoid giving every frame access to every feature. You might allow fullscreen only for the video frame and geolocation only for the map frame.

There are two layers to understand:

  1. The HTTP Permissions-Policy header sets policy for the document.
  2. The iframe allow attribute can delegate specific features to a frame, but only within what the parent policy permits.

A simple iframe might look like this:

<iframe src="https://video.example/embed/123" allow="fullscreen"></iframe>

If your header denies fullscreen entirely, the iframe attribute cannot override that denial. If your header allows fullscreen for that origin, the iframe attribute can delegate it.

This hierarchy is one reason the header is worth using. It gives platform or security teams a way to set site-wide boundaries, while still allowing product teams to enable specific embeds where needed.

What it cannot lock down

This is where teams sometimes overestimate the header.

Permissions-Policy does not replace a content security policy. It does not decide which scripts may load. It does not stop a script from sending data over the network. It does not sanitize HTML. It does not prevent XSS. It does not block form spam. If form abuse is the problem, start with the mechanics described in why your contact form is your biggest spam liability, not with this header.

It also does not replace cookie governance. Cookies, local storage, consent, third-party embeds, and browser tracking protections are separate issues. If you are reviewing privacy controls broadly, the cookie landscape deserves its own pass; the practical changes are covered in what changed for cookies in 2026 and what to do about it.

Most importantly, Permissions-Policy does not make third-party JavaScript private. If you load a third-party script into your first-party page, it generally runs with your page privileges, subject to other browser constraints and your security headers. Denying camera access is good. It does not stop that script from reading DOM content, observing user actions, or making allowed network requests.

For that, you need different controls: careful vendor selection, CSP, sandboxed iframes, Subresource Integrity where applicable, data minimization, and boring but necessary reviews.

A sensible default policy

There is no universal header that fits every site, but most content and marketing sites can start restrictive.

A reasonable first pass:

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), bluetooth=(), accelerometer=(), gyroscope=(), magnetometer=(), screen-wake-lock=()

Then add back only what the site actually uses.

For example:

  • A store using Payment Request API may need payment=(self).
  • A location finder may need geolocation=(self) or a trusted map origin.
  • A conferencing app may need camera=(self) and microphone=(self).
  • A video-heavy site may need fullscreen=(self "https://trusted-video.example").

The important part is not to copy an enormous policy from a checklist and call it done. Start with your feature inventory. Which pages need which browser capabilities? Which embeds need delegation? Which features would be surprising if they were requested?

How to deploy without breaking things

Roll this out like any other production header: deliberately.

1. Inventory feature use

Search your codebase for browser API calls such as getUserMedia, geolocation, requestFullscreen, PaymentRequest, Web Bluetooth, WebUSB, and wake lock APIs.

Then check third-party embeds. Documentation for video, maps, payment, and identity providers often mentions required iframe allow values.

2. Start in a low-risk environment

Add a restrictive header in staging and test core journeys. Pay attention to browser console messages. Browsers often report when a feature is blocked by permissions policy.

3. Use page-specific policies where necessary

Do not force one global policy if your product has very different page types. A blog, checkout, map page, and video room probably need different capabilities.

Most web servers, frameworks, and edge platforms can set headers conditionally by path. That is often cleaner than weakening the entire site for one feature.

4. Verify the real response

Headers can be added, overwritten, duplicated, or stripped by CDNs, reverse proxies, app servers, and middleware. Check the final response in browser DevTools or with command-line tools.

Also test embedded contexts. A top-level page appearing correct does not guarantee that an iframe received the delegation you intended.

5. Document exceptions

Every allowed feature should have an owner and a reason. This sounds bureaucratic until six months later, when nobody remembers why geolocation was opened to a vendor domain that no longer appears on the page.

Common syntax mistakes

The modern header syntax is compact but easy to get slightly wrong.

Use empty parentheses to deny a feature:

Permissions-Policy: microphone=()

Use self for the current origin:

Permissions-Policy: geolocation=(self)

Use quoted origins for specific external origins:

Permissions-Policy: fullscreen=(self "https://video.example")

Avoid relying on old Feature-Policy examples unless you are intentionally supporting legacy behavior. The older header used a different syntax and is not what you should design around today.

Also remember that browser support varies by directive. A browser may support the header but not a particular feature directive. That is normal. Treat the header as a defense-in-depth measure, not as your only privacy or security control.

<!-- tool-cta:start -->

💡 Try this: Verify your Permissions-Policy is being delivered as intended with Get Headers, which shows the raw response headers your server is sending.

<!-- tool-cta:end -->

The practical privacy value

The privacy value of Permissions-Policy is not that it makes a site anonymous or tracker-free. It does not.

Its value is that it narrows access to sensitive browser capabilities. Location, camera, microphone, device sensors, local hardware APIs, and payment flows are powerful. Most pages do not need them. Many embedded components should never be able to ask for them.

That is a real improvement. It reduces accidental prompts, limits unnecessary capability exposure, and gives your team a concrete artifact to review when new functionality ships.

The best version of this header is boring: restrictive by default, loosened only where a user-facing feature requires it, and tested as part of the normal release process.

Frequently asked questions

Is Permissions-Policy the same as Feature-Policy?
No. Permissions-Policy is the modern replacement for the older Feature-Policy header. Some older articles and snippets still use Feature-Policy syntax, but new implementations should use Permissions-Policy.
Can Permissions-Policy stop third-party tracking?
Not by itself. It can block access to certain browser features, but it does not stop scripts from loading, setting cookies where allowed, reading page content, or sending network requests. Use it alongside CSP, consent controls, data minimization, and careful vendor management.
Should every site deny camera and microphone?
Most sites should, yes. If your site does not provide video recording, conferencing, identity verification, or another feature that clearly needs media capture, denying camera and microphone is a sensible default.
Can an iframe allow attribute override the header?
No. The parent document policy sets the upper limit. The iframe allow attribute can delegate a feature only if the parent policy permits that feature for the frame origin.
Will unsupported directives break old browsers?
Generally, unsupported directives are ignored. You should still test important user journeys across your supported browsers because individual API behavior and console reporting can vary.

Sources & further reading

  1. MDN Web Docs: Permissions-Policy header
  2. W3C: Permissions Policy specification
  3. web.dev: Permissions Policy
  4. Chrome Developers: Controlling browser features with Permissions Policy
About the author
The Wux Webtools Team

Last updated:

Keep reading