What Is Permissions-Policy?
The Permissions-Policy HTTP header gives you control over which browser features and APIs your website can use. Think of it as a whitelist for browser capabilities — if you don't need the camera, microphone, or geolocation APIs, you can explicitly disable them.
When set correctly, Permissions-Policy prevents:
- Malicious scripts from accessing sensitive device APIs (camera, microphone)
- Embedded iframes (ads, widgets) from using features you didn't authorize
- Feature abuse by compromised third-party libraries
Here's a basic example:
Permissions-Policy: camera=(), microphone=(), geolocation=()This tells the browser: "This page and all embedded content are not allowed to use the camera, microphone, or geolocation." The empty parentheses () mean "no origins are allowed."
Why It Replaced Feature-Policy
The Permissions-Policy header is the successor to the older Feature-Policy header. The change happened for several reasons:
- New syntax — Feature-Policy used a different syntax (
Feature-Policy: camera 'none'), while Permissions-Policy uses structured headers (Permissions-Policy: camera=()) - Broader scope — Permissions-Policy covers more features and APIs
- Better integration — It works with the Permissions API in JavaScript, allowing sites to query their own permissions programmatically
Important: Some older browsers still only support Feature-Policy. For maximum compatibility, you can set both headers, but Permissions-Policy is the standard going forward.
# Nginx — both headers for compatibility
add_header Feature-Policy "camera 'none'; microphone 'none'; geolocation 'none'" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;Permissions-Policy Syntax Explained
The syntax follows the structured headers specification:
Permissions-Policy: <feature>=<allowlist>Allow Values
| Value | Meaning |
|---|---|
() | Disabled for all origins |
(self) | Allowed for same origin only |
* | Allowed for all origins |
("https://example.com") | Allowed for specific origin |
(self "https://example.com") | Allowed for self and specific origin |
Multiple Features
Separate features with commas:
Permissions-Policy: camera=(), microphone=(), geolocation=(self), payment=(self "https://stripe.com")This policy: - Blocks camera and microphone entirely - Allows geolocation for same-origin only - Allows the Payment API for same-origin and Stripe's domain
Which Features to Restrict
Here's a comprehensive list of features you can control, with recommended defaults for most websites:
### Should Almost Always Be Disabled These features are rarely needed and should be blocked by default:
camera=() # Webcam access
microphone=() # Microphone access
geolocation=() # Location access
midi=() # MIDI device access
usb=() # USB device access
bluetooth=() # Bluetooth access
serial=() # Serial port access
hid=() # Human Interface Devices
magnetometer=() # Magnetometer sensor
gyroscope=() # Gyroscope sensor
accelerometer=() # Accelerometer sensor### Context-Dependent Enable these based on your application's needs:
payment=(self) # Payment Request API — enable for e-commerce
fullscreen=(self) # Fullscreen API — enable for media players
picture-in-picture=(self) # PiP — enable for video content
autoplay=(self) # Media autoplay — enable for video sites
display-capture=() # Screen capture — usually disable### Advertising-Related Control features commonly used by ad tech:
attribution-reporting=() # Conversion tracking
browsing-topics=() # Topics API (Privacy Sandbox)
interest-cohort=() # FLoC (deprecated but still worth blocking)Setting It Up
Nginx
server {
listen 443 ssl http2;
server_name example.com;
# Permissions-Policy — block unused browser features
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), midi=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=(), payment=(), autoplay=(self), fullscreen=(self)" always;
# ... rest of your config
}Apache
# In httpd.conf, .htaccess, or VirtualHost block
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), midi=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=(), payment=(), autoplay=(self), fullscreen=(self)"Make sure mod_headers is enabled:
a2enmod headers
systemctl restart apache2Cloudflare
Using Cloudflare Transform Rules:
- 1Go to Rules > Transform Rules > Modify Response Header
- 2Create a new rule
- 3Set Header name:
Permissions-Policy - 4Set Value:
camera=(), microphone=(), geolocation=(), midi=(), usb=() - 5Apply to all requests or specific hostnames
Next.js
In next.config.js:
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=(), midi=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()',
},
],
},
];
},
};
module.exports = nextConfig;Vercel
In vercel.json:
{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Permissions-Policy",
"value": "camera=(), microphone=(), geolocation=(), midi=(), usb=()"
}
]
}
]
}Testing Your Permissions-Policy
### Browser DevTools
1. Open DevTools (F12)
2. Go to the Network tab
3. Click on the main document request
4. Look at the Response Headers section for Permissions-Policy
### JavaScript API You can query your permissions programmatically:
// Check if a feature is allowed
navigator.permissions.query({ name: 'camera' }).then(result => {
console.log('Camera permission:', result.state);
// "denied" if Permissions-Policy blocks it
});### Online Tools Run a scan on ZeriFlow to check your Permissions-Policy header alongside all other security headers in one test.
Curl
curl -s -D - -o /dev/null https://example.com | grep -i permissions-policyCommon Configuration Examples
Static Marketing Website
Permissions-Policy: camera=(), microphone=(), geolocation=(), midi=(), usb=(), payment=(), autoplay=(), fullscreen=(self)E-Commerce Store
Permissions-Policy: camera=(), microphone=(), geolocation=(self), midi=(), usb=(), payment=(self "https://js.stripe.com"), autoplay=(), fullscreen=(self)Video Platform
Permissions-Policy: camera=(self), microphone=(self), geolocation=(), midi=(), usb=(), payment=(self), autoplay=(self), fullscreen=(self), picture-in-picture=(self)SaaS Application
Permissions-Policy: camera=(), microphone=(), geolocation=(), midi=(), usb=(), payment=(self), autoplay=(), fullscreen=(self), clipboard-write=(self)Security Benefits
Configuring Permissions-Policy provides several concrete security benefits:
- 1Reduces attack surface — Disabled features can't be exploited, even if an attacker injects code into your page
- 2Controls third-party behavior — Embedded iframes and scripts are bound by your policy, preventing surprise feature usage
- 3Defense in depth — Works alongside CSP and other headers to create multiple layers of protection
- 4Privacy protection — Blocks tracking features used by advertising scripts
- 5Compliance signal — Demonstrates proactive security practices for audits
Combined with Content-Security-Policy and other security headers, Permissions-Policy is part of a comprehensive browser security strategy. Check your full header configuration with a ZeriFlow scan to see how all your headers work together.
