Permissions-Policy Directives
Restrict access to geolocation, camera, microphone, and other powerful browser features
Introduction to Permissions-Policy Directives
Have you ever visited a website and been immediately bombarded with permission requests? "This site wants to access your camera." "Allow location access?" "Enable notifications?" Within seconds, you're clicking through a gauntlet of pop-ups, wondering why a simple blog needs to know your exact coordinates. This chaotic user experience isn't just annoyingโit's a significant security and privacy problem that Permissions-Policy directives were designed to solve. As you explore this lesson, you'll discover how modern browsers give developers fine-grained control over powerful features, and you can reinforce your learning with free flashcards embedded throughout to test your understanding.
The web has evolved dramatically from its humble origins as a document-sharing platform. Today's browsers are essentially operating systems, providing access to cameras, microphones, GPS sensors, payment systems, and dozens of other powerful capabilities. But with great power comes great responsibilityโand substantial risk. Every API that bridges the gap between web content and device hardware represents a potential attack surface. Malicious actors have exploited these features to track users, steal sensitive data, and compromise privacy in ways that early web architects never anticipated.
The Evolution from Feature-Policy
To understand Permissions-Policy, we first need to look at its predecessor. In 2018, browsers introduced Feature-Policy, a mechanism that allowed web developers to explicitly enable or disable specific browser features for their own pages and embedded content. The idea was revolutionary: instead of relying solely on user prompts and permissions, developers could proactively declare which capabilities their site needed and which should be blocked entirely.
Feature-Policy used a specific HTTP header syntax that looked like this:
Feature-Policy: camera 'none'; geolocation 'self'
This header told the browser "don't allow camera access at all, but permit geolocation for content from my own origin." While Feature-Policy was groundbreaking, its syntax proved cumbersome and difficult to maintain. Developers struggled with the quote-heavy format, and the specification itself had limitations in expressing complex allowlists.
In 2020, the web standards community introduced Permissions-Policy as the successor to Feature-Policy. The new specification maintained the same core philosophyโexplicit control over browser capabilitiesโbut refined the syntax, expanded the available directives, and aligned more closely with other security headers. The transition represents an evolution rather than a revolution, improving developer experience while strengthening the security model.
Permissions-Policy: camera=(), geolocation=(self)
Notice how the modern syntax is cleaner, using parentheses instead of quotes and equals signs for clearer assignment. This might seem like a minor change, but it significantly reduces syntax errors and makes policies more readable.
๐ค Did you know? Many browsers still support the old Feature-Policy header for backward compatibility, but all new implementations should use Permissions-Policy as Feature-Policy is officially deprecated.
The Security and Privacy Problems Solved
Why do we need Permissions-Policy directives at all? Can't we just rely on browser permission prompts? To answer this, consider the attack vectors that modern web applications face:
๐ฏ Key Principle: Defense in depth means implementing multiple layers of security rather than relying on a single protective mechanism.
Third-Party Content Attacks: Modern websites frequently embed content from multiple originsโadvertisements, analytics scripts, social media widgets, and payment processors. Each embedded iframe potentially has its own security policies and intentions. Without Permissions-Policy, a compromised third-party widget could request camera access, attempt to track location, or trigger payment APIs. Even if the parent site is trustworthy, embedded content creates exposure.
Consider this scenario:
Your Site (trusted)
โโ Advertising Network (trusted)
โโ Compromised Ad Creative (malicious)
Without explicit policies, that compromised ad could attempt to access powerful features. Users might grant permission thinking they're authorizing your trusted site, not realizing the request originates from nested content several layers deep.
Permission Fatigue: Browsers display permission prompts to users, but research shows that users become desensitized to security warnings when they appear too frequently. This phenomenon, called permission fatigue or alert fatigue, causes users to automatically click "Allow" without reading or understanding what they're authorizing. By using Permissions-Policy to block unnecessary capabilities at the server level, you reduce the cognitive burden on users and decrease the likelihood of accidental permission grants.
๐ก Real-World Example: A financial blog that embeds third-party content for advertisements has no legitimate need for camera or microphone access. By setting Permissions-Policy: camera=(), microphone=(), the site ensures that even if a malicious ad attempts to request these permissions, the browser will block the request before any prompt appears to the user.
Cross-Site Scripting (XSS) Mitigation: While Permissions-Policy isn't a complete defense against XSS attacks, it provides a valuable containment layer. If an attacker manages to inject malicious JavaScript into your page, Permissions-Policy can prevent that script from accessing sensitive APIs. The attacker might be able to modify content or steal session cookies, but they won't be able to activate the camera, request payment information, or access other restricted features.
Controlling Access to Powerful Browser Features
At its core, Permissions-Policy provides a declarative security model for browser capabilities. Rather than imperatively requesting and checking permissions in JavaScript, developers declare upfront which features their application needs and which origins should have access.
The range of controllable features is extensive and continues to grow:
๐ Quick Reference Card: Categories of Controlled Features
| ๐ฅ Category | ๐ง Example Features | ๐ฏ Use Case |
|---|---|---|
| ๐ Media Capture | camera, microphone, display-capture | Video conferencing, media recording |
| ๐ Sensors & Location | geolocation, accelerometer, gyroscope | Maps, fitness tracking |
| ๐ณ Commerce | payment, identity-credentials-get | E-commerce, authentication |
| ๐ Notifications | notifications, push | Updates, messaging |
| ๐ฎ Immersive | xr-spatial-tracking, fullscreen | VR/AR experiences, gaming |
| ๐ Browsing Context | autoplay, picture-in-picture | Media playback |
Each directive in Permissions-Policy corresponds to a specific browser feature or API. When you set a policy for a directive, you're creating an allowlist that specifies which origins can use that feature. The allowlist can be:
๐ง Empty () - No origins allowed, including your own
๐ง Self-only (self) - Only your origin
๐ง Specific origins (self "https://trusted.com") - Your origin plus trusted third parties
๐ง All origins (*) - Any origin (generally discouraged for sensitive features)
๐ก Mental Model: Think of Permissions-Policy as a security guard at the entrance to each browser API. The guard has a list (your policy) specifying which visitors (origins) are allowed through the door. Even if someone has the right credentials, they can't enter if they're not on the list.
Let's visualize how a Permissions-Policy directive controls API access:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Browser Receives Page Request โ
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Parse Permissions-Policy Header โ
โ camera=(self) โ
โ geolocation=() โ
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Create Policy Map: โ
โ โข camera: [https://yoursite.com] โ
โ โข geolocation: [] โ
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโดโโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ JS calls โ โ iframe attempts โ
โ camera API โ โ geolocation โ
โโโโโโโฌโโโโโโโ โโโโโโโโโโฌโโโโโโโโโ
โ โ
โผ โผ
โ
ALLOWED โ BLOCKED
(origin in list) (empty list)
This enforcement happens at the browser level, before any permission prompts appear and before any JavaScript can access the underlying API. It's a fundamental security boundary that code cannot circumvent.
Relationship to the Broader Browser Security Model
Permissions-Policy doesn't exist in isolationโit's part of a comprehensive browser security architecture that includes multiple complementary mechanisms:
Content Security Policy (CSP): While CSP controls what scripts can execute and where resources can be loaded from, Permissions-Policy controls what browser features those scripts can access. Think of CSP as controlling "what code runs" and Permissions-Policy as controlling "what that code can do."
Same-Origin Policy (SOP): The foundational Same-Origin Policy prevents scripts from one origin from accessing resources from another origin. Permissions-Policy builds on this by allowing you to further restrict features even within permitted origins. While SOP says "origin A can't access origin B's data," Permissions-Policy says "origin A can't use the camera at all."
CORS (Cross-Origin Resource Sharing): CORS allows controlled relaxation of SOP for resource loading, while Permissions-Policy allows controlled delegation of feature permissions. They work together to enable secure cross-origin interactions.
๐ก Pro Tip: For maximum security, use Permissions-Policy alongside CSP, HTTPS enforcement, and proper CORS configuration. These headers form a layered defense strategy where each mechanism addresses different aspects of web security.
The relationship between these mechanisms creates a security model based on explicit permission rather than implicit trust. Old security models assumed that if you loaded code from an origin, you trusted it completely. Modern security recognizes that trust is nuancedโyou might trust a third party to display an advertisement but not to access your users' cameras.
โ Wrong thinking: "My site is secure because I only include trusted third-party scripts." โ Correct thinking: "My site is secure because I've explicitly limited what all scriptsโmine and third-partyโcan access through layered security headers."
Why This Matters for Modern Web Development
As web applications become more sophisticated, the attack surface expands proportionally. A decade ago, websites were primarily static content with limited interactivity. Today's web applications can access dozens of device sensors, process payments, authenticate users biometrically, and even run machine learning models. Each capability represents both an opportunity for innovation and a potential vector for abuse.
Permissions-Policy directives empower developers to embrace powerful features while maintaining security. You can build a rich video conferencing application that uses the camera and microphone, while ensuring that embedded analytics or advertisement code can never access those same features. You can create an immersive location-based game that uses geolocation while preventing tracking scripts from accessing position data.
๐ The Principle of Least Privilege: Only grant the minimum permissions necessary for functionality. If a feature isn't needed, explicitly disable it. If only your origin needs access, don't allow third parties.
โ ๏ธ Common Mistake: Mistake 1: Developers often deploy web applications without any Permissions-Policy, relying entirely on browser defaults and user prompts. This leaves users vulnerable to permission fatigue and fails to prevent third-party content from requesting sensitive access. โ ๏ธ
As we continue through this lesson, you'll learn the precise syntax for crafting Permissions-Policy directives, explore the full catalog of available features you can control, and discover best practices for implementing policies that balance security with functionality. The goal isn't to block everythingโit's to make conscious, documented decisions about which browser capabilities your application needs and which should remain locked down.
Understanding Permissions-Policy transforms you from a passive consumer of browser security features into an active architect of your application's security posture. You'll move beyond hoping users make good decisions about permission prompts to engineering systems that can't be misused even if attackers or malicious third parties attempt to exploit them.
Anatomy of Permissions-Policy Directives
Understanding how Permissions-Policy directives are constructed is essential for effectively controlling browser features and APIs. Like many web security mechanisms, the syntax may seem cryptic at first glance, but once you understand the underlying structure, it becomes a powerful and intuitive tool for securing your web applications.
The Basic Syntax Structure
Permissions-Policy directives follow a consistent pattern that consists of a feature identifier followed by an allowlist that specifies which origins can access that feature. The basic structure looks like this:
feature-name=(allowlist)
When implementing Permissions-Policy, you have two primary delivery mechanisms: HTTP headers and HTML meta tags. The HTTP header approach is more powerful and flexible, as it can control features for the entire document and all embedded content:
Permissions-Policy: camera=(self), microphone=(self "https://trusted-domain.com")
The meta tag implementation uses slightly different syntax but achieves similar results:
<meta http-equiv="Permissions-Policy" content="camera=(self), microphone=(self)">
โ ๏ธ Common Mistake 1: Attempting to use commas inside the allowlist parentheses. The allowlist uses space-separated values, not commas! โ ๏ธ
๐ฏ Key Principle: Each directive is independent and controls access to one specific feature. Multiple directives are combined using commas to form a complete policy.
Understanding Directive Names
The directive name (or feature identifier) directly maps to specific browser features and Web APIs. These names are standardized and case-sensitive, typically using lowercase with hyphens for multi-word features. For example:
cameracontrols access to the getUserMedia() camera APIgeolocationcontrols the Geolocation APIpaymentcontrols the Payment Request APIfullscreencontrols the Fullscreen APIaccelerometercontrols the Accelerometer sensor API
The directive name tells the browser exactly which capability you're restricting. When you write camera=(self), you're creating a rule that says "only this origin can access camera hardware through the browser."
๐ก Mental Model: Think of directive names as gates to specific browser capabilities. Each gate has a lock, and the allowlist determines who gets keys to open that gate.
Directive Values: The Allowlist
The allowlist enclosed in parentheses defines which origins have permission to use the feature. There are four primary values you'll work with, each with distinct behavior:
The self Keyword
The self keyword grants permission only to the same origin as the document itself. If your page is served from https://example.com, only content from that exact origin can use the feature:
Permissions-Policy: geolocation=(self)
This is the most restrictive option that still allows your own content to function. It prevents any embedded third-party content from accessing the feature, even if that content is trusted for other purposes.
The src Keyword
The src keyword is specialโit only applies to iframe elements and grants permission to the origin specified in the iframe's src attribute. This creates a dynamic allowlist based on what you're embedding:
<iframe src="https://maps.example.com" allow="geolocation 'src'"></iframe>
The src value doesn't make sense in the HTTP header context because the header is set before iframes are parsed. It's primarily used in the iframe allow attribute.
The none Value
Using empty parentheses or the special value * with an empty set creates a complete block of the feature:
Permissions-Policy: microphone=()
This prohibits all origins, including your own, from accessing the feature. This is useful for features your application definitely doesn't need, providing defense-in-depth against potential vulnerabilities.
๐ก Pro Tip: Explicitly blocking unused features is a security best practice. If your site doesn't use the camera, set camera=() to prevent any code (including injected malicious code) from accessing it.
The wildcard (*) Symbol
The asterisk allows all origins to access the feature:
Permissions-Policy: fullscreen=*
This essentially disables the restriction for that feature. While this might seem counterproductive, it's useful when you need maximum compatibility or when you're transitioning from a permissive to a restrictive policy.
โ ๏ธ Common Mistake 2: Assuming * means "use defaults." Actually, * explicitly allows all origins. To use defaults, simply omit the directive entirely! โ ๏ธ
Specific Origins
You can specify exact origins as quoted strings in the allowlist:
Permissions-Policy: payment=(self "https://payment-processor.com" "https://backup-processor.com")
This grants permission to your own origin plus the two specified external origins. Origins must include the full scheme and domain, and they must be quoted with double quotes.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Permissions-Policy: camera=(self "https://cdn.com") โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โ โ โโ Specific origin (quoted)
โ โโโโโโโโโโโโโโโโโ Same-origin access
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Feature identifier
Default Behavior and Implicit Policies
Understanding what happens when you don't set a directive is crucial for effective policy design. The default behavior varies by feature and is defined by the specification for each API:
Most features default to * (allow all) when no directive is specified. This maintains backward compatibility with existing web content. For example, if you don't specify a fullscreen directive, all origins can request fullscreen mode.
Some powerful features default to self for security reasons. Features like payment and identity-credentials-get are restricted to same-origin by default, even without an explicit policy.
A few features have special defaults that depend on user permissions or other factors. The geolocation feature, for instance, already requires user permission, but the Permissions-Policy can block access before the browser even prompts the user.
No Directive Set Explicit Directive
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ โ
โ Uses default โ โ Uses specified โ
โ (usually *) โโโโโถโ allowlist โ
โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
Permissive Your Control
๐ค Did you know? The default-permissive approach was chosen to prevent breaking existing websites. As the web evolves, newer features often have more restrictive defaults.
Combining Multiple Directives
A real-world Permissions-Policy typically includes multiple directives to create a comprehensive security posture. Directives are combined in a comma-separated list, and each operates independently:
Permissions-Policy: camera=(self),
microphone=(self),
geolocation=(self "https://maps.trusted.com"),
payment=(self),
usb=()
When multiple directives are present:
๐ Each directive is evaluated independently - there's no inheritance or cascading between different features
๐ The most restrictive policy wins - if both the HTTP header and an iframe's allow attribute set policies, the more restrictive one applies
๐ Order doesn't matter - camera=(self), microphone=() has the same effect as microphone=(), camera=(self)
๐ Duplicate directives cause the last one to apply - if you accidentally specify camera=(self), camera=(), the second directive overrides the first
๐ก Real-World Example: A news website might use:
Permissions-Policy: camera=(),
microphone=(),
geolocation=(self),
fullscreen=(self "https://video-cdn.example.com")
This blocks camera and microphone entirely, allows geolocation for the main site (perhaps for local news), and allows fullscreen for both the main site and a trusted video CDN.
Special Considerations for Nested Contexts
When dealing with iframes and nested browsing contexts, Permissions-Policy creates a delegation chain. A parent document can delegate permission to an iframe, but the iframe cannot grant itself more permissions than its parent has:
Parent: camera=(self "https://trusted.com")
โ
โโ Iframe from https://trusted.com
Can use camera โ
โ
โโ Nested iframe from https://other.com
Cannot use camera โ (parent didn't delegate)
The iframe can use the allow attribute to further restrict permissions, but never to expand them:
<!-- Parent has: camera=(self "https://trusted.com") -->
<iframe src="https://trusted.com" allow="camera 'none'">
<!-- This iframe blocks camera even though parent allowed it -->
</iframe>
โ ๏ธ Common Mistake 3: Thinking an iframe's allow attribute can override a restrictive parent policy. It can only make policies more restrictive, never more permissive! โ ๏ธ
Practical Syntax Examples
Let's examine some complete, real-world policy declarations:
Strict security policy for a static content site:
Permissions-Policy: camera=(),
microphone=(),
geolocation=(),
payment=(),
usb=()
E-commerce site with third-party payment processor:
Permissions-Policy: camera=(),
microphone=(),
geolocation=(self),
payment=(self "https://payment.stripe.com"),
usb=()
Video conferencing application:
Permissions-Policy: camera=(self),
microphone=(self),
display-capture=(self),
fullscreen=(self)
๐ Quick Reference Card:
| Symbol | Meaning | Example | Effect |
|---|---|---|---|
๐ฏ self |
Same origin only | camera=(self) |
Only your site can access |
๐ * |
All origins | fullscreen=* |
Any embedded content can access |
๐ซ () |
No one | usb=() |
Feature completely blocked |
๐ "origin" |
Specific origin | payment=(self "https://pay.com") |
Listed origins can access |
๐ src |
Iframe source | allow="geo 'src'" |
Iframe's origin can access |
๐ก Remember: The Permissions-Policy header is your first line of defense in controlling browser features. Start with a restrictive policy blocking features you don't need, then explicitly allow only what's necessary with the most limited scope possible. This "deny by default, allow by exception" approach provides the strongest security posture.
With this understanding of the syntax and structure, you're now equipped to construct precise, effective Permissions-Policy directives that balance functionality with security.
Categories and Types of Available Directives
The Permissions-Policy specification provides a comprehensive toolkit of directives that control access to powerful browser features and APIs. Understanding these directives and their categories helps you craft policies that protect users while enabling the functionality your application needs. Let's explore the landscape of available directives, organized by their primary purpose and use case.
Media and Sensor Directives
Modern web applications often need access to device hardware, but these capabilities represent significant privacy risks. The media and sensor directives give you fine-grained control over which contexts can access sensitive device features.
The camera and microphone directives control access to video and audio input devices respectively. These are among the most privacy-sensitive permissions, as unauthorized access could enable surveillance. When you set camera=(self), you're declaring that only your own origin can request camera accessโembedded third-party content cannot. This prevents scenarios where an advertisement or analytics iframe secretly activates your users' cameras.
Permissions-Policy: camera=(self), microphone=(self)
The geolocation directive governs access to the Geolocation API, which provides precise location data. A travel booking site might need this for showing nearby hotels, but the advertising widgets it embeds certainly don't. By restricting geolocation to your own origin, you prevent location data leakage to third parties.
Motion and orientation sensors are controlled through several directives: accelerometer, gyroscope, and magnetometer. While these might seem innocuous, researchers have demonstrated they can be used for fingerprinting or even keystroke inference attacks. Gaming applications might legitimately need these sensors, but most content sites should disable them:
Permissions-Policy: accelerometer=(), gyroscope=(), magnetometer=()
๐ฏ Key Principle: Media and sensor directives follow a deny-by-default-for-third-parties philosophy. Even without explicit policies, browsers increasingly restrict these features, but explicit policies provide defense in depth.
๐ก Real-World Example: A news website discovered that one of their advertising partners was requesting microphone access through an embedded iframe. By implementing microphone=(), they completely blocked this capability, preventing any potential for audio surveillance through their site.
Payment and Commerce Directives
The modern web includes powerful APIs for streamlining commercial transactions. These directives control access to payment and identity-related features.
The payment directive governs access to the Payment Request API, which provides a standardized way for users to complete transactions. This API accesses sensitive payment credentials stored in the browser or payment apps. An e-commerce site might enable this for their checkout page while denying it everywhere else:
Permissions-Policy: payment=(self "https://checkout.example.com")
The identity-credentials-get directive (formerly fedcm) controls access to the Federated Credential Management API, which facilitates privacy-preserving identity federation. This allows users to sign in with identity providers while limiting tracking capabilities. Only pages that implement authentication flows should have access:
Permissions-Policy: identity-credentials-get=(self)
โ ๏ธ Common Mistake: Developers sometimes assume payment directives only matter for e-commerce sites. However, any site with third-party content should explicitly deny payment access to prevent embedded frames from initiating unexpected payment flows. โ ๏ธ
Display and Presentation Directives
These directives control how content can manipulate the user's display and presentation environment. While less obviously security-critical, they affect user experience and can be vectors for deception.
The fullscreen directive determines which origins can request fullscreen mode. Fullscreen capability is essential for video players and presentation tools, but malicious actors have used it to create convincing phishing attacks by rendering fake browser UI in fullscreen. Restricting this prevents embedded content from taking over the display:
Permissions-Policy: fullscreen=(self)
The picture-in-picture directive controls access to the Picture-in-Picture API, allowing video content to float above other windows. Media sites might enable this for their video players but deny it for advertisements:
Permissions-Policy: picture-in-picture=(self "https://cdn.videos.example.com")
The screen-wake-lock directive governs the Screen Wake Lock API, which prevents the screen from dimming or locking. This is useful for recipe sites, presentation tools, or navigation apps, but unnecessary for most content:
Permissions-Policy: screen-wake-lock=(self)
๐ก Mental Model: Think of display directives as controlling the "canvas" available to different origins. Just as you wouldn't let a guest rearrange your entire house, you shouldn't let third-party content take over the user's entire screen without explicit permission.
Privacy-Sensitive Directives
As browsers evolve to balance personalized experiences with privacy, new APIs have emerged that require careful governance. Recent directives focus on controlling access to privacy-sensitive tracking and advertising technologies.
The browsing-topics directive controls access to the Topics API, part of the Privacy Sandbox initiative. This API provides interest categories based on browsing history for advertising purposes, but with more privacy protection than third-party cookies. Sites can opt out entirely:
Permissions-Policy: browsing-topics=()
The interest-cohort directive governed the now-deprecated FLoC (Federated Learning of Cohorts) API. Although FLoC is no longer pursued, this directive became famous through the "#NOFLOCK" movement, where sites declared:
Permissions-Policy: interest-cohort=()
Other privacy-related directives include attribution-reporting (for the Attribution Reporting API) and shared-storage (for shared storage operations). These represent ongoing attempts to enable certain web functionality while improving privacy.
๐ค Did you know? The interest-cohort directive became one of the most rapidly adopted Permissions-Policy directives in history, with major sites and CDNs adding it as a privacy statement even before FLoC launched widely.
Additional Important Directives
Beyond these major categories, numerous other directives control specific capabilities:
๐ Quick Reference Card: Other Key Directives
| ๐ฏ Category | ๐ง Directive | ๐ Purpose |
|---|---|---|
| Automation | autoplay |
Controls automatic media playback |
| Clipboard | clipboard-read, clipboard-write |
Governs clipboard access |
| Display | display-capture |
Controls screen capture/sharing |
| Input | keyboard-map |
Access to keyboard layout info |
| Device | usb, serial, hid |
Hardware device access |
| Gaming | gamepad |
Gamepad/controller access |
| Biometric | publickey-credentials-get |
WebAuthn credentials |
| XR | xr-spatial-tracking |
VR/AR spatial tracking |
The autoplay directive deserves special mention as one of the most commonly needed. It controls whether media can play automatically, preventing annoying auto-playing ads:
Permissions-Policy: autoplay=(self)
Finding Authoritative Information
The Permissions-Policy specification continues to evolve as new browser capabilities emerge. To find current, authoritative information:
๐ง Primary Resources:
- W3C Permissions Policy Specification: The authoritative source for directive definitions and behavior
- MDN Web Docs: Provides practical documentation with browser compatibility tables
- Chrome Platform Status: Tracks Chrome's implementation of specific directives
- Can I Use: Shows cross-browser support levels for each directive
โ ๏ธ Browser compatibility varies significantly across directives. Some like camera and microphone enjoy universal support, while newer directives like browsing-topics may only work in specific browsers.
๐ก Pro Tip: Use the browser's DevTools Console to test directive support. Invalid directives trigger console warnings, helping you identify which policies are actually enforced in your target browsers.
To check what directives a browser supports, examine the document.featurePolicy.allowedFeatures() method (in browsers still supporting the older Feature-Policy name) or consult the browser's implementation documentation:
// Check if a specific feature is allowed
if (document.featurePolicy &&
document.featurePolicy.allowsFeature('camera')) {
console.log('Camera access is permitted in this context');
}
๐ง Mnemonic: Remember MEDIA-PAYS-FOR-DISPLAY-PRIVACY to recall the five major directive categories: Media sensors, Payment/commerce, Display/presentation, and Privacy-sensitive features.
Strategic Directive Selection
When designing your Permissions-Policy, start by categorizing your application's actual needs. A content-focused blog has very different requirements than a video conferencing application. Begin with a restrictive baseline that denies capabilities you definitely don't need:
Permissions-Policy:
camera=(),
microphone=(),
geolocation=(),
payment=()
Then selectively enable specific directives for origins that require them. This whitelist approach provides the strongest security posture.
โ
Correct thinking: "My application needs camera access on the video call page, but nowhere else. I'll enable camera only for that specific page using the allow attribute on iframes or by serving different headers."
โ Wrong thinking: "I'll just allow everything by default and restrict things if problems come up."
The landscape of Permissions-Policy directives reflects the ongoing tension between web capabilities and user privacy. By understanding these categories and staying current with evolving directives, you can craft policies that protect your users while enabling the features your application truly needs.
Common Pitfalls and Best Practices
Implementing Permissions-Policy directives effectively requires more than understanding the syntaxโit demands awareness of common implementation mistakes and a strategic approach to policy design. This section explores the pitfalls that frequently trap developers and provides battle-tested practices for creating robust, maintainable security policies.
The Wildcard Trap: When "Allow All" Undermines Security
๐ฏ Key Principle: The wildcard (*) in Permissions-Policy is a security anti-pattern that defeats the entire purpose of implementing the header.
One of the most common mistakes developers make is using overly permissive wildcard declarations. Consider this problematic policy:
Permissions-Policy: geolocation=*, microphone=*, camera=*
โ Wrong thinking: "I'll use wildcards now and tighten security later when I know which domains need access."
โ Correct thinking: "I'll explicitly allowlist only the domains that need access right now, and add more as legitimate requirements emerge."
The wildcard allows any origin loaded on your pageโincluding malicious third-party scripts injected through XSS vulnerabilitiesโto access powerful features. This creates a false sense of security: you've implemented the header, but provided no actual protection.
๐ก Real-World Example: A major e-commerce platform initially deployed payment=* to avoid breaking their payment processor integration. When a supply chain attack compromised one of their analytics vendors, the malicious code could access the Payment Request API because the wildcard granted universal access. A properly scoped policy (payment=(self "https://trusted-payment.com")) would have prevented this.
The legitimate use cases for wildcards are extremely rare. Even development environments should use specific origins rather than wildcards to catch integration issues early.
Silent Failures: Syntax Errors That Go Unnoticed
โ ๏ธ Common Mistake 1: Mixing allowlist syntaxes between the old Feature-Policy format and the current Permissions-Policy standard.
Browsers silently ignore malformed Permissions-Policy headers rather than throwing errors. This creates a dangerous situation where developers believe they've implemented protection when none exists.
Common syntax errors include:
๐ง Incorrect quote handling:
## โ Wrong - missing quotes around 'self'
Permissions-Policy: camera=(self https://example.com)
## โ
Correct
Permissions-Policy: camera=(self "https://example.com")
๐ง Comma placement mistakes:
## โ Wrong - commas separate directives, not origins
Permissions-Policy: camera=("https://a.com", "https://b.com")
## โ
Correct - space-separated origins within parentheses
Permissions-Policy: camera=("https://a.com" "https://b.com")
๐ง Mixing directive separators:
## โ Wrong - using semicolons like CSP
Permissions-Policy: camera=(self); microphone=(self)
## โ
Correct - comma-separated directives
Permissions-Policy: camera=(self), microphone=(self)
๐ก Pro Tip: Use your browser's DevTools to verify policy application. In Chrome DevTools, navigate to Application โ Frames โ top โ Permissions Policy to see which directives are actually in effect. Firefox provides similar inspection in the Storage Inspector.
โ ๏ธ Common Mistake 2: Forgetting scheme and port requirements when specifying origins.
Origins must include the full scheme (https://), and if using non-standard ports, include those too:
## โ Wrong - missing scheme
Permissions-Policy: camera=(example.com)
## โ
Correct
Permissions-Policy: camera=("https://example.com")
## โ
Also correct - including non-standard port
Permissions-Policy: camera=("https://example.com:8443")
The Integration Nightmare: Breaking Third-Party Dependencies
โ ๏ธ Common Mistake 3: Deploying restrictive policies without auditing third-party service requirements.
Many modern web applications rely on third-party services that require access to specific browser features. Implementing Permissions-Policy without accounting for these dependencies creates immediate functionality breakage.
Common third-party requirements:
| Service Type | Typical Requirements | Breaking Policy Example |
|---|---|---|
| ๐น Video Conferencing | camera, microphone, display-capture | camera=(), microphone=() |
| ๐บ๏ธ Maps & Location | geolocation | geolocation=(self) |
| ๐ณ Payment Processors | payment | payment=(self) |
| ๐ Analytics (some) | sync-xhr | sync-xhr=() |
| ๐ฎ Game Embeds | fullscreen, gamepad, accelerometer | fullscreen=(self) |
Systematic approach to avoiding breakage:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. Audit Current Dependencies โ
โ (DevTools Network tab analysis) โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 2. Document Required Permissions โ
โ (consult vendor documentation) โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 3. Draft Policy with Allowlists โ
โ (explicit origins only) โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 4. Test in Staging Environment โ
โ (monitor console for violations) โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 5. Deploy with Monitoring โ
โ (track error rates & support) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ก Pro Tip: Before deploying policies to production, use the Permissions-Policy-Report-Only header to detect violations without enforcing restrictions. Unfortunately, this header isn't widely supported yet, so thorough staging environment testing remains critical.
Testing Strategies: Verification Across the Browser Landscape
Permissions-Policy implementation varies across browsers, making comprehensive testing essential. Not all directives are supported uniformly, and behavior can differ subtly between implementations.
Multi-layered testing approach:
๐ Layer 1: Static Validation Use online validators and linters to catch syntax errors before deployment. Tools like the Mozilla Observatory and securityheaders.com can verify header syntax.
๐ Layer 2: Browser DevTools Inspection Manually verify policy application in each target browser:
- Chrome/Edge:
Application โ Frames โ Permissions Policy - Firefox:
Storage Inspector โ Permissions - Safari:
Develop โ Show Web Inspector โ Storage
๐ Layer 3: Automated Feature Testing Create integration tests that attempt to access restricted features:
// Test that geolocation is properly restricted
async function testGeolocationPolicy() {
try {
await navigator.geolocation.getCurrentPosition();
console.error('โ Policy failed: geolocation accessible');
} catch (err) {
if (err.name === 'NotAllowedError') {
console.log('โ
Policy working: geolocation blocked');
}
}
}
๐ Layer 4: Cross-Browser Compatibility Testing Test on actual devices and browsers, not just emulators. Mobile browsers especially may have different permission handling.
๐ค Did you know? Some browsers implement "permission delegation" differently. In Chrome, an iframe with allow="camera" can access the camera if the parent page has permission, but Safari might require the iframe's origin to be explicitly listed in the Permissions-Policy header.
Balancing Security with Usability
The most restrictive policy isn't always the best policy. Effective security requires balancing protection against legitimate functionality and user experience.
The principle of least privilege applies to Permissions-Policy:
โ
Grant only necessary permissions: Start with everything denied (*=()), then explicitly allow only what's needed.
โ Scope permissions to specific origins: Never use wildcards; enumerate trusted origins.
โ Regularly audit and remove unused allowlists: As integrations change, remove obsolete permissions.
โ Document the rationale for each allowlist entry: Future maintainers need to understand why specific origins have access.
Progressive enhancement strategy:
Stage 1: Deploy permissive policy with all current integrations
โ Establish baseline without breaking changes
Stage 2: Gradually tighten restrictions on unused features
โ Remove permissions for features not actively used
Stage 3: Replace wildcards with explicit origins
โ Enumerate all legitimate third-party origins
Stage 4: Implement defense-in-depth with CSP + Permissions-Policy
โ Layer multiple security headers for comprehensive protection
โ ๏ธ Common Mistake 4: Forgetting that Permissions-Policy affects iframes you control, not just third-party content.
If you embed your own content in iframes (like widget systems or microservices), you must explicitly allow necessary permissions using the iframe allow attribute:
<!-- Your own iframe needs explicit permission too -->
<iframe src="https://widgets.yoursite.com/map"
allow="geolocation 'src'"></iframe>
๐ Quick Reference Card: Pitfalls vs. Best Practices
| โ ๏ธ Pitfall | โ Best Practice |
|---|---|
| ๐ Using wildcard (*) declarations | ๐ Explicit origin allowlisting only |
| ๐คท No validation of header syntax | ๐ DevTools inspection + automated tests |
| ๐ฅ Deploying without testing third-parties | ๐งช Staging environment verification |
| ๐ Mixing old Feature-Policy syntax | ๐ Using current Permissions-Policy format |
| ๐ฏ One-size-fits-all policies | โ๏ธ Context-appropriate restriction levels |
| ๐๏ธ Set-and-forget approach | ๐ Regular audits and updates |
| ๐ค Silent about policy rationale | ๐ Documented decision-making |
Summary
You now understand the critical implementation details that separate effective Permissions-Policy deployment from superficial security theater. Before working through this section, you might have thought that simply adding a Permissions-Policy header provided protectionโnow you recognize that policy quality depends entirely on careful design and testing.
โ ๏ธ Critical points to remember:
- Wildcards undermine security entirelyโthey allow any origin (including malicious scripts) to access powerful features
- Syntax errors fail silentlyโbrowsers won't warn you when policies are malformed, requiring proactive validation
- Third-party integrations need explicit allowlistingโaudit dependencies before deploying restrictive policies
- Testing must span multiple browsersโimplementation details vary, especially for permission delegation to iframes
- Security and usability must be balancedโoverly restrictive policies harm user experience without proportional security benefit
Practical next steps:
๐ฏ Immediate action: Audit your current Permissions-Policy headers (if any) using browser DevTools to verify they're parsed correctly and working as intended.
๐ฏ Short-term project: Create a testing suite that validates restricted features are properly blocked across your target browsers, integrating these tests into your CI/CD pipeline.
๐ฏ Long-term practice: Establish a quarterly review process for your Permissions-Policy configuration, removing obsolete allowlists and tightening restrictions on unused features as your application evolves.
With these pitfalls avoided and best practices applied, your Permissions-Policy implementation will provide meaningful security enhancement rather than mere compliance theater. The combination of restrictive defaults, explicit allowlisting, comprehensive testing, and regular maintenance creates a robust defense against feature abuse while maintaining the functionality your users depend on.