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

Steps

  1. Step 1.

    Read the header on a document response.

    curl -sI https://www.cloudflare.com/ | grep -i 'permissions-policy'
    
    permissions-policy: geolocation=(), camera=(), microphone=()
  2. 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.

  3. Step 3.

    Check for the older header name on the same host. Feature-Policy was renamed in 2020 and uses different syntax.

    curl -sI https://www.cloudflare.com/ | grep -ci 'feature-policy'
    
    0

    A count of 1 here would mean the site still sends Feature-Policy: geolocation 'none' style directives, with spaces and quoted tokens rather than =().

  4. 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

Sign: The policy looks correct and a third-party iframe still prompts for the microphone.Cause: The iframe carries its own allow attribute, which can only narrow the parent policy but is often paired with a parent policy of star. Read the allow attribute on each frame together with the header.
Sign: A feature stops working after the header is added and no error appears anywhere.Cause: A denied feature fails quietly. The API returns a rejected promise or a permission state of denied, with no prompt and no console entry in some browsers. Test the feature itself, not only the header value.
Sign: The header is written with the old syntax and is ignored.Cause: Feature-Policy used space separated tokens in single quotes, such as geolocation 'self'. Permissions-Policy uses structured fields: geolocation=(self), with parentheses and double quotes around origins. A value in the old syntax under the new name parses as nothing.

What to check next

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.

intermediate5 minpublished updated Maks Verny