Permissions-Policy and Referrer-Policy: the two headers that decide what your site gives away
Two one-line headers control what scripts on your pages can request and which URLs leak to other sites. How to set the Permissions-Policy header and Referrer-Policy.

Your website leaks in two directions. Inward, your pages can ask a visitor's browser for the camera, the microphone, their location, or a payment sheet. Outward, the browser tells other sites which page a visitor came from, every time a page loads a third-party script or the visitor clicks an outbound link. Two response headers set the rules. The Permissions-Policy header decides what your pages, and every script running on them, are allowed to request. Referrer-Policy decides how much of your URL goes along with each outbound request.
Each one is a single line of server config. Here is what each one closes and the value to set.
Leak one: what scripts on your page can ask for
When you add a chat widget, an analytics tag, or a marketing pixel with a plain <script> tag, that code runs as part of your site. It gets the same powers your own code has. If it calls the geolocation API, the browser shows the permission prompt under your domain name. The visitor sees your site asking where they are, even though your code never asked.
Iframes work a little differently. A cross-origin iframe (an embedded booking widget or video player, for example) can only use powerful features if the parent page grants them with an allow attribute. Your Permissions-Policy header sets the ceiling: if the header denies a feature, no allow attribute can grant it back.
Without the header, the browser falls back to its defaults. For most sensitive features, the default is that your own origin can request them. That covers every script you load directly, including scripts that a tag manager added last year and nobody remembers.
What the Permissions-Policy header does
The header lists browser features and who may use them. An empty pair of parentheses means nobody, not even your own pages:
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), bluetooth=(), magnetometer=(), gyroscope=(), accelerometer=()
With that header in place, a script that calls navigator.geolocation gets an error instead of a prompt. Your visitor never sees a request they did not expect, and an injected script cannot use the feature even if it slips past your other defenses.
Those nine features are the ones worth denying on almost any business site:
- camera and microphone: the obvious ones. Unless you run video calls or voice recording in the browser, nothing on your site needs them.
- geolocation: precise location. Store locators with a "use my location" button need it. Most sites do not.
- payment: the browser's built-in payment sheet (the Payment Request API).
- usb and bluetooth: direct access to hardware connected to the visitor's device.
- magnetometer, gyroscope, and accelerometer: motion sensor readings, which can help fingerprint a device and track it across sites.
MDN's Permissions-Policy reference lists the full set of directives and the allowlist syntax.
Only deny what you do not use
The header only helps when it actually denies something. Some sites still carry a Permissions-Policy header left over from 2021 that reads interest-cohort=(). That line opted out of an old Google ad experiment. It restricts none of the features above, so the header is present and protects nothing.
The opposite mistake is denying a feature your site depends on. If your checkout offers Apple Pay or Google Pay through an embedded payment provider, payment=() will break it. Scope the feature instead of opening it to everyone:
Permissions-Policy: payment=(self "https://pay.example-provider.com"), camera=(), microphone=(), geolocation=()
Your payment provider's docs will name the exact origin to list. The same pattern works for a store locator: geolocation=(self) lets your own pages ask while blocking embedded third parties.
Two compatibility notes. Chromium-based browsers (Chrome, Edge, and the browsers built on them) enforce the header. Firefox and Safari support is still limited, so treat it as one layer of defense, not the only one. Browsers also ignore feature names they do not recognize and log a console warning, so listing a feature an older browser does not know about breaks nothing.
Leak two: what your URLs hand to other sites
Every request your page makes to another domain can carry a Referer header (the misspelling is in the original spec). That includes outbound link clicks, and it also includes every image, font, script, and tracking pixel loaded from a third-party host. The header tells the other server which page the request came from.
How much of the URL it carries depends on the referrer policy. Under the old browser default, no-referrer-when-downgrade, the full URL went to any HTTPS destination: path, query string, everything. That is a problem on any page whose URL carries data:
- Password reset and magic-link pages:
/reset?token=8f3a.... If that page loads a third-party script, the token can travel with the request. - Client portals:
/portal/invoices/48213?client=acme-dental. - Search pages:
/search?q=chapter+7+bankruptcy. The query is often the most sensitive thing a visitor types. - Form confirmations:
/thank-you?email=jane%40example.com. Some form tools can pass submitted fields into the redirect URL.
None of those URLs should reach an ad network, a font host, or a site you linked to.
Why to set Referrer-Policy explicitly
Current versions of the major browsers now default to strict-origin-when-cross-origin or something stricter. Google's guidance on referrer best practices covers the switch. So why set the header yourself?
Because a default is a browser decision, not yours. Older browsers still use the looser default, and you cannot control which browser each visitor shows up with. An explicit header makes the behavior the same everywhere and visible in an audit, so you know what your site sends instead of assuming.
The values you will run into:
| Value | Same-site requests | Cross-site requests |
|---|---|---|
| no-referrer | Nothing | Nothing |
| same-origin | Full URL | Nothing |
| strict-origin | Origin only | Origin only (nothing on HTTPS to HTTP) |
| strict-origin-when-cross-origin | Full URL | Origin only (nothing on HTTPS to HTTP) |
| no-referrer-when-downgrade | Full URL | Full URL (nothing on HTTPS to HTTP) |
| unsafe-url | Full URL | Full URL, even over HTTP |
The value to pick
Referrer-Policy: strict-origin-when-cross-origin
Your own pages still see full URLs for internal navigation, so on-site analytics paths keep working. Other sites see only https://yourdomain.com/, which is enough for them to know you sent the visit and nothing more. Nothing at all goes out over unencrypted HTTP.
no-referrer is stricter, but it can break things that read the header on your own site, such as some form protection checks, and it hides your traffic from partners you link to. If a specific page needs to send nothing (a password reset page, for example), set no-referrer on that page alone. The MDN Referrer-Policy reference covers per-element overrides with the referrerpolicy attribute.
Even with a strict referrer policy, the cleaner fix is to keep tokens and personal data out of URLs entirely. The header limits who sees a URL. It does not stop the URL from landing in browser history, server logs, or a screenshot.
Where to add both headers
Both go in the same place as your other security headers: your server config (.htaccess on Apache, add_header in nginx), your host's headers file (vercel.json, Netlify's _headers), your CDN's response header rules, or a security plugin on WordPress. If you already set HSTS or a Content Security Policy, add these two lines next to them.
We cover the other two headers that need real tuning in separate posts: the Content Security Policy your CMS hides from you and how to enable HSTS without breaking your site. Referrer-Policy and Permissions-Policy are the easy ones: a few minutes of work, and both are hard to break once you have checked what you actually use.
Check what your site sends today
Paste your URL into the AcuityScan HTTP headers tool to see whether either header is present and what value it carries, alongside the rest of your security headers.
The full AcuityScan scan counts both headers among the 10 in its security header grade, and it goes one step further on Permissions-Policy. It checks the same nine features listed above and reports how many your header actually denies, as a count like "restricts 9/9 sensitive features." A result like 2/9 means the header exists but most of those features are still open. The count only credits features set to (), so a feature you scoped to (self) on purpose will show as open. That is fine when you use the feature. Treat the count as a review prompt, not a score to max out. The scan runs 350+ checks across 8 modules, with a plain-English fix for each finding.
Scan your own site
See what 350+ checks find on your domain.
Free, no signup, 60 seconds. Email auth · DNS · SSL · Performance · SEO · Accessibility · Privacy · Mobile.
