Lion Cipher Lab
Restrict browser features with a Permissions-Policy header
- AppSec / DevSecOps
- Intermediate
- ~50 min
- Free · no account required
Why it matters
Modern browsers expose powerful features to web pages: your location, your camera and microphone, payment sheets, fullscreen, the clipboard. Two things decide whether a page may even ask for them. One is you, the user, clicking Allow on a prompt. The other is the site's own policy, and most sites never set one. That matters because a typical page is not only your code. It embeds third-party widgets in iframes, often with a copy-pasted allow attribute that hands over far more than the widget needs, and it runs third-party scripts in your own document. If any of that code turns hostile, or an…
What you'll learn
- explaining what a Permissions-Policy response header controls (which powerful browser features your page and the frames inside it may use) and how that differs from CSP frame-ancestors (who may frame YOU) and from the rest of CSP (what your page may load)
- measuring which features a document and an embedded cross-origin iframe may use with document.featurePolicy.allowsFeature() in a Chromium-based browser
- seeing that a cross-origin iframe gets features like geolocation and camera only because the embedding page's allow attribute delegates them, and that the header is the ceiling the allow attribute cannot exceed
- writing the header in structured-field syntax: geolocation=() to block everywhere, fullscreen=(self) for your own origin, fullscreen=(self "http://localhost:9000") to delegate to one named origin
- delegating exactly one feature to a third-party frame by naming its origin in the header AND listing the feature in the iframe allow attribute
- automating a before/after check with a stdlib Python script that fetches headers with urllib, parses the policy and fails when an expected feature is not blocked
- catching the classic mistake of pasting old Feature-Policy syntax into the Permissions-Policy header, which makes the browser ignore the whole header
- knowing the limits: Chromium-only header support, no effect on Firefox or Safari, no fix for XSS, and no way to grant a feature, only to take one away
Steps in this lab
- The vocabulary: powerful features, policy, prompts and origins
- Why this header, and not frame-ancestors or the rest of CSP
- Set up: save the two-origin lab server
- Measure BEFORE: what the embedded widget is allowed to use
- Find the real culprit: the allow attribute you pasted
- Automate the check: a header validator in stdlib Python
- Add the header and measure AFTER (allow attribute left generous)
- Delegate exactly one feature, the way a pro does it
- Break it on purpose: the old Feature-Policy syntax
- Judgment: where the header stops
- What you did, and the reflex to keep