Referrer-Policy and Permissions-Policy

This reference is part of the Web Security Headers Fundamentals reference and covers two privacy-and-capability headers that ship together in almost every hardened deployment: Referrer-Policy, which governs what the browser puts in the outgoing Referer request header, and Permissions-Policy, which governs which powerful browser APIs a document and its embedded frames may use. They solve different problems — one stops URL metadata from leaking, the other stops hardware and capability abuse — but both are declarative allowlists evaluated by the browser before any page script runs, and both are commonly set side by side at the same edge or origin layer.

The two headers are independent. Referrer-Policy controls an outbound request header; Permissions-Policy controls a per-document capability surface. Neither is a substitute for Content Security Policy, which restricts what resources load, and neither enforces transport security the way HTTP Strict Transport Security does. Treat all four as separate layers.

Threat Model & Protocol Mechanics

Referrer leakage

When a browser navigates or fetches a subresource, it attaches a Referer header (the historical misspelling is baked into the protocol) describing the URL the request originated from. Without a policy, older browser defaults transmitted the full URL — scheme, host, path, and query string — to any same-protocol destination. That is a direct data-exfiltration channel:

The Referrer Policy specification (W3C) defines the eight policy tokens and the exact stripping behaviour for each, including how the policy interacts with protocol downgrade (HTTPS → HTTP). Modern Chromium, Firefox, and Safari default to strict-origin-when-cross-origin, but the default is not guaranteed across privacy-hardened or legacy agents, so the header must be set explicitly for deterministic behaviour.

Hardware-feature and capability abuse

Permissions-Policy exists because a document — including a third-party <iframe> you embed — can otherwise attempt to use powerful APIs: geolocation, camera, microphone, payment, usb, fullscreen, autoplay, and motion sensors. The threats:

Permissions-Policy denies these by default per feature and delegates them explicitly. It supersedes the deprecated Feature-Policy header. The Permissions Policy specification (W3C) defines the header as an HTTP structured field — a structured dictionary whose values are allowlists of origins. Getting the structured-field syntax wrong (legacy Feature-Policy whitespace-separated grammar, missing quotes, trailing commas) makes the browser silently ignore the directive, which fails open.

Referrer-Policy value to leaked-data matrix A matrix showing which part of the URL each Referrer-Policy value sends for same-origin, cross-origin HTTPS, and HTTPS to HTTP downgrade navigations. Policy value same-origin cross-origin HTTPS→HTTP

no-referrer none none none

origin origin origin origin

strict-origin-when -cross-origin full URL origin none

origin-when -cross-origin full URL origin origin

no-referrer-when -downgrade full URL full URL none

unsafe-url full URL full URL full URL

What each Referrer-Policy value sends. The boxed row is the modern browser default and the recommended baseline.

Directive Syntax & Spec

Referrer-Policy

The header value is a single policy token (or a comma-separated list where later tokens are fallbacks for unsupported earlier ones):

Referrer-Policy: strict-origin-when-cross-origin
Directive Type Default Security Impact When to Deviate
no-referrer token Sends nothing, ever. Maximum privacy. Use for high-sensitivity flows; breaks affiliate/attribution.
no-referrer-when-downgrade token legacy default Sends full URL except on HTTPS→HTTP. Leaks full path cross-origin. Documented legacy fallback only.
origin token Always sends only scheme://host:port. When any cross-origin attribution by origin is acceptable.
origin-when-cross-origin token Full URL same-origin, origin cross-origin, but still sends on downgrade. Rarely; the strict variant is safer.
same-origin token Full URL same-origin, nothing cross-origin. Strict same-origin-only apps.
strict-origin token Origin only, and nothing on downgrade. When you never need same-origin paths in Referer.
strict-origin-when-cross-origin token modern default Full URL same-origin, origin cross-origin, nothing on downgrade. The recommended baseline.
unsafe-url token Always sends the full URL, including on downgrade. Leaks everything. Never in production.

Common gotcha: an unrecognised token causes the browser to fall back to its default policy, not to fail. Always validate the exact spelling. A <meta name="referrer"> tag overrides nothing if the HTTP header is present and parsed — but a malformed header lets the meta tag win.

For the recommended baseline, see setting Referrer-Policy: strict-origin-when-cross-origin, which covers the per-stack rollout for that one value in depth.

Permissions-Policy

The value is a structured-field dictionary. Each entry is feature=<allowlist>, where the allowlist is one of () (deny all), * (allow all origins), (self) (this origin only), or (self "https://trusted.example") (this origin plus named origins, each origin a quoted string). Multiple features are comma-separated:

Permissions-Policy: geolocation=(), camera=(), microphone=(), payment=(self), fullscreen=(self)
Directive Type Default behaviour Security Impact When to Deviate
geolocation=() allowlist deny all Blocks location access in document and all frames. (self) if your own app needs location.
camera=() allowlist deny all Blocks getUserMedia video. (self) for video-call features.
microphone=() allowlist deny all Blocks getUserMedia audio. (self) for voice features.
payment=() allowlist deny all Blocks the Payment Request API. (self) or (self "https://checkout.example") for checkout.
fullscreen=(self) allowlist varies by feature Restricts requestFullscreen to your origin. * only if embedders legitimately need it.
usb=() / serial=() allowlist deny all Blocks WebUSB/WebSerial device access. Almost never deviate.

Delegation gotcha: omitting a feature from the header does not deny it — each feature has its own spec default (some default-allow for the top document). To guarantee denial you must list the feature with (). The allowlist values are quoted origin strings; bare hostnames or the legacy whitespace-separated Feature-Policy grammar are silently rejected.

Platform-Specific Implementation

Nginx

add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(self), fullscreen=(self)" always;

Security impact: always emits the header on error responses (4xx/5xx) too, closing the gap where an error page would otherwise ship without policy. Note that any add_header inside a location block replaces all inherited add_header directives — repeat both headers in every block that defines its own.

Verify: curl -sI https://example.com | grep -iE 'referrer-policy|permissions-policy'

Apache

Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(self), fullscreen=(self)"

Security impact: always adds the header to internally generated error responses as well as 2xx; set (vs add) guarantees a single deterministic value and overrides upstream module output. Place in <VirtualHost> or .htaccess with mod_headers enabled.

Verify: curl -sI https://example.com | grep -i permissions-policy

IIS

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="Referrer-Policy" value="strict-origin-when-cross-origin" />
      <add name="Permissions-Policy" value="geolocation=(), camera=(), microphone=(), payment=(self), fullscreen=(self)" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

Security impact: customHeaders in web.config applies the header to every response the site returns, including static files served directly by IIS without hitting application code. Use <remove> before <add> if a parent config already declares the header, to avoid duplicates.

Verify: curl -sI https://example.com | findstr /I permissions-policy

Node/Express (Helmet)

Helmet sets Referrer-Policy natively. Permissions-Policy has no built-in Helmet helper, so set it raw:

const helmet = require("helmet");

app.use(
  helmet({
    referrerPolicy: { policy: "strict-origin-when-cross-origin" },
  })
);

app.use((_req, res, next) => {
  res.setHeader(
    "Permissions-Policy",
    "geolocation=(), camera=(), microphone=(), payment=(self), fullscreen=(self)"
  );
  next();
});

Security impact: Helmet normalises casing and prevents duplicate Referrer-Policy declarations. Registering the Permissions-Policy middleware before your routes guarantees it runs on every response, including error handlers downstream.

Verify: curl -sI http://localhost:3000 | grep -iE 'referrer-policy|permissions-policy'

Cloudflare

Use a Transform Rule (Rules → Transform Rules → Modify Response Header) to Set both headers, or a Worker for programmatic control:

export default {
  async fetch(request) {
    const response = await fetch(request);
    const headers = new Headers(response.headers);
    headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
    headers.set(
      "Permissions-Policy",
      "geolocation=(), camera=(), microphone=(), payment=(self), fullscreen=(self)"
    );
    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers,
    });
  },
};

Security impact: Edge injection enforces the policy even when the origin is misconfigured, and headers.set (not append) overwrites any origin value so you never ship duplicates. The body stream is preserved untouched.

Verify: curl -sI https://example.com | grep -i referrer-policy

Verification & Diagnostic Workflows

  1. Header presence and exact value:

    curl -sI https://example.com | grep -iE 'referrer-policy|permissions-policy'

    Expected:

    referrer-policy: strict-origin-when-cross-origin
    permissions-policy: geolocation=(), camera=(), microphone=(), payment=(self), fullscreen=(self)
    
  2. DevTools: Network tab → select the document request → Response Headers. Confirm a single instance of each header (duplicates indicate two layers both setting them).

  3. Referrer behaviour: Navigate from your HTTPS origin to a third-party HTTPS page and read document.referrer in that page’s console. Under strict-origin-when-cross-origin expect only https://example.com/, never a path.

  4. Feature denial: In a cross-origin iframe, run navigator.geolocation.getCurrentPosition(console.log, console.error). Expect a GeolocationPositionError with code 1 (PERMISSION_DENIED) when geolocation=() is set.

  5. CI/CD gate:

    #!/usr/bin/env bash
    set -euo pipefail
    h=$(curl -sI "https://staging.example.com")
    echo "$h" | grep -iq 'referrer-policy: strict-origin-when-cross-origin' || { echo "Referrer-Policy missing/wrong"; exit 1; }
    echo "$h" | grep -iq 'permissions-policy:.*geolocation=()' || { echo "Permissions-Policy missing geolocation deny"; exit 1; }

Troubleshooting, Misconfigurations & Safe Rollback

Safe rollback: these headers are non-destructive — reverting is a config change plus a cache purge, with no client-side lock-in (unlike HSTS preload). To roll Referrer-Policy back to the broadest compatible value, set Header always set Referrer-Policy "no-referrer-when-downgrade", purge the CDN, and re-run the curl -sI check. For Permissions-Policy, remove individual feature=() entries to re-enable a capability without dropping the rest of the policy.

Frequently Asked Questions

Does omitting a feature from Permissions-Policy deny it? No. Each feature has its own spec default, and several default to allow for the top-level document. To guarantee denial you must list the feature explicitly with an empty allowlist, e.g. geolocation=().

Is Permissions-Policy a replacement for Feature-Policy? Yes. Permissions-Policy supersedes the deprecated Feature-Policy header and uses a different structured-field grammar (quoted origin strings, feature=(allowlist)). Do not ship both; emit only Permissions-Policy.

Will strict-origin-when-cross-origin break my analytics? It preserves the origin cross-origin, so platforms that attribute by referring origin keep working. Tools that rely on full referring URLs lose path detail; move that attribution to first-party parameters or server-side logging.

Do these headers need to be set if browsers already default to them? Yes. Browser defaults are not contractual and differ across privacy-hardened and legacy agents. Explicit headers give deterministic, auditable behaviour and satisfy compliance scans.

Can I set these via a <meta> tag instead? Referrer-Policy has a <meta name="referrer"> equivalent, but it is overridden by the HTTP header and is weaker. Permissions-Policy has no meta equivalent. Set both as HTTP response headers.