Browser Permissions & Fetch Metadata Headers

How Permissions-Policy restricts what browser features your page can use, and how Fetch Metadata headers give your server trustworthy request context.

🛠️ Related tool: Open Security Headers Checker →
Permissions-Policy controls which powerful browser features your page and any embedded content are actually allowed to use, while Fetch Metadata headers let your server see the context every incoming request came from before deciding how to handle it. They solve genuinely different problems — one restricts capabilities, the other informs server-side decisions — but both are underused tools most sites could benefit from adopting.

Permissions-Policy: Restricting What Your Page Can Actually Do

Permissions-Policy lets a page explicitly declare which browser features — camera, microphone, geolocation, USB access, fullscreen, autoplay, and dozens more — it and any embedded iframes are permitted to use, regardless of whether the underlying browser API exists. This matters even for a page that never intentionally uses any of these features, because it closes off an entire category of risk: if a third-party script gets compromised (through a supply-chain attack on an analytics tag or ad script, for instance), a correctly configured Permissions-Policy prevents that compromised script from being able to invoke a sensitive API at all, since the policy blocks it at the browser level regardless of what the script's code actually tries to do.

The syntax structures around individual feature directives, each specifying an allowlist of origins permitted to use that specific feature. A directive like camera=() with empty parentheses disables camera access entirely, for every context including your own top-level page; camera=(self) permits it only for your own origin; and camera=(self "https://trusted-partner.com") extends that permission to one additional named origin, useful when you deliberately embed a trusted third-party widget that genuinely needs camera access.

🎯
ToolsNovaHub Pro Tip
Start with a restrictive default (disabling features you genuinely don't use, like camera=() and microphone=()) rather than trying to enumerate every feature — most sites use only a handful of these APIs, so denying by default and allowlisting the few you need is far less error-prone.
⚠️
Common Beginner Mistake
Confusing Permissions-Policy with the browser's separate runtime permission prompts (the "Allow this site to use your camera?" dialog). Permissions-Policy operates independently, at the HTTP header level, before any prompt would even appear — it restricts what's technically possible, not what the user is asked to approve.

Delegating Permissions to Embedded Iframes

A particularly useful application of Permissions-Policy is controlling exactly which embedded iframes can access which features, independent of what the iframe's own content might request. An embedded video conferencing widget genuinely needs camera and microphone access to function; an embedded ad iframe almost certainly doesn't, and restricting it via Permissions-Policy prevents that ad content (which you don't control and can't fully audit) from ever being able to request those permissions in the first place, regardless of what code it's running.

This can also be controlled per-iframe using the allow attribute directly on the <iframe> element, which works alongside the header-level policy — the two need to agree for a feature to actually be permitted, since the more restrictive of the two applies. This layered control is genuinely useful for sites embedding several different third-party widgets with different legitimate needs, letting each one be scoped precisely rather than applying one blanket policy to every embed on the page.

Fetch Metadata Headers: Letting Your Server See Request Context

Fetch Metadata headers — Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest, and Sec-Fetch-User — are automatically attached by the browser to every request it sends, and unlike most headers, they can't be set or overridden by JavaScript, making them a genuinely trustworthy signal about how a request was actually initiated. Sec-Fetch-Site tells your server whether the request came from the same origin, the same site (different subdomain), a cross-site origin, or was typed directly by the user (like a bookmark or address-bar navigation). Sec-Fetch-Mode indicates the request type — navigation, a fetch/XHR call, form submission, and similar. Sec-Fetch-Dest reveals what the requested resource will actually be used for — an image, a script, an iframe document, a worker, and more.

Because the browser sets these headers itself and doesn't allow application JavaScript to spoof them, they provide server-side code with reliable information that used to require fragile inference from the Referer header alone — information that's genuinely useful for making security decisions about how to handle a given request.

Using Fetch Metadata for Practical Server-Side Defense

A common, high-value pattern: rejecting cross-site requests to sensitive endpoints that should only ever be reached from your own site. If a request to your account-settings API arrives with Sec-Fetch-Site: cross-site, that's a strong signal something unusual is happening, since a legitimate user interacting with your own application would never generate a cross-site request to that specific endpoint under normal circumstances. This provides a genuinely useful additional layer of defense against certain cross-site attack patterns, without requiring the more invasive session-token-based protections that would otherwise be the primary defense.

Another practical use: distinguishing navigation requests from resource-loading requests using Sec-Fetch-Dest, letting a server serve different content or apply different caching rules depending on whether a request is for the actual HTML page versus an embedded resource on that page — a distinction that was previously difficult to make reliably server-side without this explicit signal.

The Full Set of Fetch Metadata Headers, Individually

Sec-Fetch-Site takes one of four values: same-origin, same-site, cross-site, or none (the last indicating the request wasn't triggered by another page at all, such as a direct URL entry or a bookmark). Sec-Fetch-Mode distinguishes navigate, cors, no-cors, and same-origin request types, reflecting how the browser itself is treating the request. Sec-Fetch-Dest covers a long list of specific destination types — document, image, script, style, iframe, worker, and many more — identifying precisely what role the fetched resource will play once loaded.

Sec-Fetch-User is narrower in scope, present only on navigation requests and set to ?1 specifically when the navigation was triggered by genuine user activation (a click or key press), rather than programmatically by script — a useful signal for distinguishing a real user-initiated page load from one triggered automatically, such as through a redirect chain or an auto-refreshing page.

Permissions-Policy vs Fetch Metadata: Different Layers, Complementary Roles

AspectPermissions-PolicyFetch Metadata Headers
DirectionServer tells browser what's allowedBrowser tells server what kind of request this is
Who acts on itThe browser, enforcing the restriction client-sideYour own server-side application logic
What it controlsAccess to powerful browser APIs and featuresContextual information about request origin and intent
Can it be spoofed by page JavaScript?N/A — it's a restriction, not spoofable dataNo — set exclusively by the browser itself
Primary benefitPrevents compromised scripts from using sensitive APIsEnables smarter, more informed server-side security decisions

Permissions-Policy's Relationship to the Older Feature-Policy Header

Permissions-Policy is the successor to an earlier header called Feature-Policy, which used a similar directive-based structure but was eventually renamed and refined as the specification matured — a shift driven partly by the "permissions" framing more accurately reflecting what the header actually controls, and partly by syntax refinements that came out of real-world implementation experience with the original. Feature-Policy still works in some older browser versions for backward compatibility, but current documentation and new deployments should use Permissions-Policy specifically, since that's where ongoing feature additions and refinements are happening.

Sites that adopted Feature-Policy early and haven't yet migrated aren't necessarily broken, but are missing newer directives and refinements that only exist under the Permissions-Policy name — a straightforward, low-risk update worth making during any broader header configuration review.

Real Scenarios for Adopting These Headers

A publisher embeds several third-party ad networks and wants to ensure none of them can ever request camera, microphone, or geolocation access, even if one of those networks is later compromised through a supply-chain attack. A Permissions-Policy denying these features by default across the entire page, with no per-origin allowlist exceptions, closes this off at the browser level regardless of what any embedded script attempts.

An API endpoint handling sensitive account changes starts rejecting requests where Sec-Fetch-Site indicates cross-site origin, adding a meaningful layer of defense against certain attack patterns targeting that endpoint, implemented as a straightforward server-side header check rather than requiring any client-side changes at all.

A video conferencing web app needs camera and microphone access for its own origin, embeds a separate analytics widget that needs neither, and configures Permissions-Policy to grant exactly the first and deny the second explicitly, rather than relying on the embedded widget simply choosing not to request permissions it technically could.

A security team investigating unusual traffic patterns uses Fetch Metadata's Sec-Fetch-Dest header to distinguish genuine page-navigation requests from a flood of iframe-embedding attempts from unrecognized origins, information that wasn't reliably available before these headers existed without more invasive request fingerprinting.

Combining Both Header Families for Layered Feature Control

Permissions-Policy and Fetch Metadata address different sides of the same broader goal — restricting what a page's content is capable of doing, and giving your server better information to make its own decisions — and combining them provides more complete coverage than either alone. A page might use Permissions-Policy to deny camera access to all embedded third-party content outright, while separately using Fetch Metadata on its own API endpoints to reject requests that don't originate from expected same-site navigation contexts, addressing client-capability restriction and server-side request validation as two distinct, complementary layers rather than expecting one mechanism to cover both concerns.

Neither header family is a substitute for foundational security practices like proper authentication, input validation, or CSRF tokens where applicable — they're additional, genuinely valuable layers that reduce the impact of certain failure modes (a compromised third-party script, an unexpected cross-site request) without replacing the need for those foundational protections in the first place.

Rolling These Out Without Breaking Existing Functionality

For Permissions-Policy, the safest rollout approach is auditing which features your site and its embedded content actually use before writing any restrictive policy — browser developer tools typically log a warning when a feature is blocked by policy, making it straightforward to identify gaps during a testing period before deploying restrictively in production. For Fetch Metadata-based server logic, start by logging the header values you're seeing in production traffic without actually rejecting anything, to understand your real traffic patterns (including legitimate but less obvious cases, like requests from browser extensions or accessibility tools) before adding any enforcement logic that could reject genuine users.

Checking What Your Site Currently Sends

Verifying whether Permissions-Policy is actually present, and correctly scoped, on your live site requires checking the real HTTP response rather than assuming from your application's configuration files — a value that looks correct in your server config can still be stripped or altered by an intermediate proxy or CDN before reaching an actual visitor's browser. Our Security Headers Checker and HTTP Headers Checker both show the live headers a domain is actually serving, confirming exactly what a real request receives.

FAQ

No — it operates at a different layer entirely. Permissions-Policy restricts whether a feature is technically available to request at all, applied via HTTP headers before any user-facing prompt would appear; the browser's own permission dialog remains a separate, additional layer.
Yes — using the allow attribute directly on each iframe element alongside the header-level policy, letting you scope permissions per-embed rather than applying one blanket rule to everything on the page.
Support is broad across current major browsers, though as with any relatively newer header family, it's worth treating server-side logic based on these headers as an additional defense layer rather than the sole protection mechanism, given older browser versions won't send them.
No — these headers are set exclusively by the browser itself for every outgoing request, and application JavaScript has no ability to override or spoof their values, which is exactly what makes them a trustworthy server-side signal.
Browser default behavior applies, which is generally permissive for most features unless a user explicitly denies a runtime permission prompt — an explicit Permissions-Policy gives you proactive control rather than relying on defaults or hoping embedded scripts behave responsibly.
For sensitive, same-site-only endpoints it's generally safe and valuable, but legitimate cross-site scenarios do exist (certain OAuth flows, for instance), so it's worth auditing your actual expected traffic patterns before applying strict rejection rules broadly.
Yes — an empty allowlist like camera=() blocks the feature for your own top-level page's code too, not just embedded third-party content, so make sure to explicitly allow self for any feature your own code genuinely needs.
Referer can be missing, spoofed by older or unusual clients, or stripped by privacy settings and referrer policies; Fetch Metadata headers are browser-generated specifically for this purpose and provide more structured, reliable information than Referer alone ever did.
The benefit is smaller without third-party content to restrict, but it's still worth setting as defense-in-depth against your own first-party code being compromised through a supply-chain attack on a dependency you do use.
They provide useful context (like whether a request has Sec-Fetch-User indicating genuine user activation) but aren't a complete bot-detection solution on their own — they're most valuable combined with other signals rather than relied on in isolation.
📅 Last updated: August 2026📜 Sourced from: W3C Permissions Policy and Fetch Metadata Request Headers specifications

ToolsNovaHub guides are researched against primary sources (RFCs, vendor docs) and kept up to date as standards change. Spotted an error? Let us know.

📋 Related Tools & Guides Comparison

ResourceTypeLink
Security Headers CheckerToolOpen Tool →
HTTP Headers CheckerToolOpen Tool →
Cross-Origin Isolation Headers GuideGuideRead Guide →
Security Headers ExplainedGuideRead Guide →
Try it yourself — 100% free
🚀 Open Security Headers Checker

🔗 More Guides