Quick Summary
- Permissions-Policy (formerly Feature-Policy) controls browser API access.
- It prevents third-party iframes from accessing camera, geolocation, or microphone.
- Disabled features cannot be re-enabled by embedded widgets.
- Empty parentheses () disable a feature globally.
Introduction
Modern browsers expose powerful APIs that allow websites to access the camera, microphone, geolocation, payment systems, USB devices, and more. These APIs are essential for some applications, but they also create significant privacy and security risks when third-party code runs on your page.
The Permissions-Policy header lets you control exactly which browser APIs can be used on your page and by whom. It applies to both your own code and any embedded third-party content. By explicitly disabling APIs you don't use, you shrink your attack surface and protect users from rogue scripts.
This guide explains what Permissions-Policy controls, why it matters for privacy and security, and how to configure it correctly as part of your security headers strategy. Read more on the W3C Permissions Policy specification.
What Is Permissions Policy?
API Access Governance
The Permissions-Policy header (formerly Feature-Policy) lets you control which browser APIs and features can be used on your page. It applies to both your own code and any embedded third-party iframes.
Once a feature is disabled via Permissions-Policy, it cannot be re-enabled by any embedded content, even if the iframe has its own allow attribute. This makes it a hard gate, not a suggestion.
How Directives Work
As shown above, when a third-party widget requests access to a browser API, the browser checks the Permissions-Policy before showing any permission prompt. If the policy denies the feature, the request is silently blocked, the user never even sees a prompt.
Why It Matters for Privacy
Preventing Abuse
Third-party widgets run in your page's context. This creates a trust exploitation scenario:
- Trust exploitation: Users see browser permission prompts from your domain, not the widget's. If they grant access, they are unknowingly giving that access to the third-party code.
- Data collection: A rogue ad widget could silently access geolocation data, revealing the user's physical location to an ad network.
- Fingerprinting: APIs like battery status, hardware concurrency, and media devices can be used for browser fingerprinting, identifying users without cookies.
- Payment hijacking: Without restrictions, a compromised widget could access the Payment Request API to display payment sheets that redirect funds.
Trust Exploitation
Common Controlled Features
| Feature | API Controlled | Recommendation |
|---|---|---|
| camera | getUserMedia (video) | Disable unless needed: camera=() |
| microphone | getUserMedia (audio) | Disable unless needed: microphone=() |
| geolocation | Navigator.geolocation | Disable unless needed: geolocation=() |
| payment | Payment Request API | Restrict to self + trusted processor |
| usb | WebUSB | Disable: usb=() |
| fullscreen | Fullscreen API | Restrict to self: fullscreen=(self) |
| autoplay | Media autoplay | Restrict to self: autoplay=(self) |
| display-capture | Screen capture API | Disable: display-capture=() |
| battery | Battery Status API | Disable to prevent fingerprinting |
Check if your Permissions Policy is properly configured.
Run Security Headers CheckReal-World Examples
- Ad network geolocation abuse: Several ad networks were found requesting geolocation access through embedded iframes. Users unknowingly granted location access to their domain, allowing the ad network to track their physical movements.
- Cryptocurrency mining: Malicious iframes used the Web Workers API to run cryptocurrency miners in the background, draining battery and CPU without the user's knowledge.
- Camera/mic access via chat widgets: Some chat widgets requested camera and microphone access for “video support” features, but the data was accessible to the widget vendor's servers.
- Fingerprinting via unused APIs: Trackers used APIs like
navigator.getBattery()andnavigator.hardwareConcurrencypurely for fingerprinting purposes, not for any user-facing feature.
How to Check Your Policy
- Run a headers scan: Use the Security Headers Checker to see if your Permissions-Policy header is present and review which features are configured.
- Check DevTools: Open DevTools → Application → Permissions Policy. Chrome shows which features are enabled, disabled, or set to specific origins.
- Test third-party iframes: Load your page with embedded widgets and check if they can access sensitive APIs. Use DevTools Console to test:
navigator.geolocation.getCurrentPosition(console.log). - Full privacy audit: Run a comprehensive scan to check Permissions-Policy alongside other security headers.
How to Implement Permissions-Policy
Deployment Techniques
Disable All Sensitive APIs
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), display-capture=()Empty parentheses () disable the feature for all contexts, including your own page.
Allow Specific Features for Your Domain
Permissions-Policy: geolocation=(self), payment=(self "https://payments.example.com"), camera=(), microphone=()Geolocation restricted to your origin. Payment API allowed for you and one trusted processor. Camera and mic disabled entirely.
Add the header at your web server or CDN level:
Nginx Configuration
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;Best Practices
- Deny by default, allow by exception, disable all sensitive APIs, then selectively enable only what your application genuinely uses.
- Restrict to self for used features, if your app uses geolocation, set
geolocation=(self)to prevent third-party iframes from accessing it. - Combine with CSP, Permissions-Policy controls API access, CSP controls content loading. Together they provide comprehensive protection.
- Review when adding third-party widgets, every new chat widget, analytics tool, or embedded component could potentially access browser APIs. Check what they need.
- Document your feature requirements, maintain a list of which APIs your application uses and why. This helps during security audits and when onboarding new developers.
- Test iframe behavior, verify that embedded content cannot access features you have disabled. Use browser DevTools to confirm.
Common Mistakes
- Not setting Permissions-Policy at all: Without the header, all browser APIs are available to all content on the page, including third-party iframes.
- Using the deprecated Feature-Policy syntax: The old
Feature-Policyheader uses different syntax. UsePermissions-Policywith the modern Structured Field Values format. - Enabling features for all origins:
camera=*allows all embedded content to access the camera. Use specific origins instead:camera=(self). - Only setting Allow attributes on iframes: The
allowattribute on an iframe cannot grant permissions that the page-level Permissions-Policy has denied. You need both. - Forgetting about new APIs: Browsers regularly add new APIs (Serial, Bluetooth, HID). Review your policy when browsers release new versions to disable features you do not need.
Conclusion
Permissions-Policy is a simple but powerful header that prevents third-party code from accessing sensitive browser APIs on your behalf. It takes a single line of server configuration to disable camera, microphone, and geolocation access for all embedded content, closing an entire category of privacy and security risks.
Combined with a strong Content Security Policy and other security headers, Permissions-Policy creates a robust defense that protects users even when third-party code behaves maliciously.
Scan Your Website
Related Guides
Frequently Asked Questions
What is the difference between Feature-Policy and Permissions-Policy?+
How does Permissions-Policy interact with iframe allow attributes?+
Should I disable all features by default?+
Does Permissions-Policy affect performance?+
Can Permissions-Policy prevent fingerprinting?+
Is Permissions-Policy supported in all browsers?+
Scan for Security Vulnerabilities
Ensure you aren't leaving powerful browser APIs open to exploitation by third-party tracking scripts.
For deeper runtime checks, run the full privacy audit →