How to check permissions-policy header
Read the header: curl -sI https://example.org/ | grep -i permissions-policy. Each entry names a browser feature and the origins allowed to use it, so geolocation=() means no frame on the page may call the geolocation API. A missing header leaves every feature available to your own origin and to same-origin frames.
Why check this
The header is an allow list for device and browser features: camera, microphone, geolocation, payment, fullscreen, autoplay and around thirty more. It applies to the document and to every frame inside it, which is where the value shows. A third-party chat widget or an advertisement in an iframe can prompt the user for the camera under your domain name unless the policy denies it.
Run this check when a page starts embedding third-party frames, before a release that adds a payment or a media feature, and after any change to the frame allow list. Run it together with a click through of the feature your product needs, because a policy that is too tight silently disables a working function and produces a bug that reads as a permission prompt that never appears.
Prerequisites
- curl 7.0 or later. Any build works. See the curl manual.
- The MDN Permissions-Policy reference for the current feature names, which change as features ship.
- A written list of the features your product uses. Without it the check has nothing to compare against.
Steps
- Step 1.
Read the header on a document response.
curl -sI https://www.cloudflare.com/ | grep -i 'permissions-policy'permissions-policy: geolocation=(), camera=(), microphone=() - Step 2.
Split the value into one directive per line, because a real policy runs past the width of a terminal.
curl -sI https://www.cloudflare.com/ | grep -i 'permissions-policy' | tr ',' '\n' | sed 's/^ *//'permissions-policy: geolocation=() camera=() microphone=()Three features are denied to every origin, including this one. Every other feature is unlisted, which means it keeps its default and is not restricted by this header.
- Step 3.
Check for the older header name on the same host.
Feature-Policywas renamed in 2020 and uses different syntax.curl -sI https://www.cloudflare.com/ | grep -ci 'feature-policy'0A count of 1 here would mean the site still sends
Feature-Policy: geolocation 'none'style directives, with spaces and quoted tokens rather than=(). - Step 4.
Count both header names on a site that sends neither, so you can tell an absent policy from a narrow one.
curl -sI https://developer.mozilla.org/en-US/ | grep -ciE 'permissions-policy|feature-policy'0
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| camera=() | Empty allow list. No origin may use the camera, not even this one. | Correct for a page that has no camera feature. |
| camera=(self) | Only this origin. Frames from other origins are denied. | Correct when your own code uses the camera and embeds are untrusted. |
| camera=(self "https://vendor.example") | This origin plus one named origin | Confirm the named origin is one you audit. Quote marks are part of the syntax. |
| camera=* | Every origin in every frame, which is the same as no policy | Replace with an explicit list. A star here is a finding on any page with third-party frames. |
| No header at all | Every feature keeps its default, most of them self | Add the header with an empty list for the features you do not use. |
Common mistakes
What to check next
- How to check security headers: reads this header with the rest of the set in a single request.
- How to check CSP header: decides which third parties can be framed at all, which is the input to this policy.
- How to check if a site is vulnerable to clickjacking: the framing test that pairs with a feature allow list.
- How to check X-Frame-Options: the older framing control that still decides who can embed your page.
- Security headers checker: paste a URL and read every header in this cluster at once.
FAQ
What is the difference between Permissions-Policy and Feature-Policy?
Same idea, two generations. Feature-Policy is the 2018 name with quoted, space separated values. Permissions-Policy is the current name and uses structured field syntax with =(). Browsers no longer honour the old name, so sending only that leaves the page unrestricted.
Does the header restrict my own scripts?
Yes. An empty allow list denies the feature to your own origin as well. If your page calls the geolocation API, the policy needs geolocation=(self), not geolocation=().
Which features should be denied by default?
The ones your product does not use. Camera, microphone, geolocation, payment, usb and serial are the common candidates, because each one produces a browser prompt that carries your domain name.
Can I set the policy in a meta tag?
No. Unlike CSP, this policy is delivered only as an HTTP response header, plus the allow attribute on individual frames. A meta tag with this name has no effect.
Verified
Verified by Maks Vernycurl 8.21.0
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
intermediate5 minpublished updated Maks Verny