Part II β The Shield Β· Lesson 3.12
Feature Policy | Permissions-Policy
In one lineSwitch off browser capabilities your app never uses β for your code and every frame you embed.
Where you've seen itA third-party support widget on your page. By default it can prompt for camera, microphone, and location β under your domain name.
Diagram
CAPABILITY GATE β default is ON for everything
WITHOUT the header
βββββββββββββ your-app.com βββββββββββββ
β your JS camera β mic β geo β β
β ββββ iframe: widget.com ββββββββββ β
β β their JS camera β mic β geo ββ β β prompts say "your-app.com"
β βββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββ
WITH Permissions-Policy: camera=(), microphone=(), geolocation=(self)
βββββββββββββ your-app.com βββββββββββββ
β your JS camera β mic β geo β β
β ββββ iframe: widget.com ββββββββββ β
β β their JS camera β mic β geo ββ β β API rejects, no prompt
β βββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββSYNTAX β an allowlist per feature
Permissions-Policy:
camera=(), nobody, not even you
microphone=(),
geolocation=(self), only your own origin
payment=(self "https://checkout.stripe.com"),
fullscreen=*, everyone including frames
interest-cohort=() opt out of tracking cohorts
Per-frame override (can only narrow, never widen):
<iframe allow="geolocation 'none'; camera 'none'">How it works
- The header ships from the server; the browser enforces it for the document and every nested frame.
- A frame can only receive capabilities the parent already has β permissions narrow going down, never widen.
- Blocked APIs reject or return empty rather than prompting, so failures are silent β test them.
- Start from deny-all for anything the product doesn't use, then add back per feature.
Trade-offs
| Feature | Sensible default |
|---|---|
camera, microphone | () unless you ship video calling |
geolocation | (self) for store locators, else () |
payment | (self) plus the payment provider's origin |
fullscreen | (self) for video players |
usb, serial, bluetooth | () β almost never needed |
Related isolation headers: Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp give you a cross-origin-isolated context β required for SharedArrayBuffer and precise timers.
Interview angle
"Why restrict features you never call?"
- If XSS or a compromised third-party script lands, those APIs are available to it under your origin's trust.
- Permission prompts show your domain, so abuse damages your reputation directly.
- It's a one-line header covering every route, including ones added after you wrote it.
Recap
- Everything is enabled by default; the header is how you opt out.
- Frames inherit a narrowed set β a parent can never grant what it doesn't have.
- Blocked APIs fail silently, so verify with real feature tests.