Referrer-Policy & Permissions-Policy Explained
Modern web applications require granular control over information leakage and browser feature access. Implementing Web Security Headers Fundamentals establishes the baseline for defense-in-depth, with Referrer-Policy governing navigation metadata and Permissions-Policy restricting privileged browser APIs. These headers operate at the transport/application boundary, enforcing declarative policies before client-side scripts execute.
Key Technical Directives:
Referrer-Policystrictly controls theRefererHTTP header transmission during cross-origin and same-origin navigation.Permissions-Policysupersedes the deprecatedFeature-Policyspecification, providing standardized API scoping and iframe delegation controls.- Both headers function independently but share a strict allowlist/denylist paradigm that defaults to explicit configuration rather than implicit browser behavior.
Threat Model & Security Impact Analysis
Unrestricted referrer transmission exposes session tokens, internal routing paths, and user identifiers to third-party origins. Similarly, unconstrained browser features enable malicious iframes to capture sensor data or initiate unauthorized transactions. Layering these controls alongside Content Security Policy (CSP) Essentials creates a comprehensive perimeter against data exfiltration and API abuse.
Primary Attack Vectors Mitigated:
- PII Leakage via Cross-Origin Referrer Strings: Full URL paths containing query parameters (e.g.,
?user_id=12345) leak to external domains during navigation. - Session Fixation & Token Exposure: Authentication tokens or CSRF tokens embedded in URLs are transmitted via the
Refererheader to untrusted endpoints. - Unauthorized Peripheral Activation: Embedded third-party frames exploit unrestricted
camera,microphone, orusbpermissions to capture environmental data without explicit user consent. - Silent Geolocation Tracking: Background scripts in restricted contexts bypass consent prompts when feature policies are absent or permissive.
Step-by-Step Implementation & Platform Directives
Deployment requires explicit HTTP header injection at the origin or edge layer. For most production environments, Setting Referrer-Policy strict-origin-when-cross-origin provides the optimal balance between analytics functionality and cross-origin privacy. Permissions-Policy utilizes a strict feature=(allowlist) syntax where empty parentheses () denote complete denial.
Nginx
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
Security Impact: The always flag guarantees header injection on all response codes (including 4xx/5xx), preventing policy bypass during error states. Restricting sensor APIs at the reverse proxy level blocks iframe delegation before application logic executes.
Verification: Execute curl -sI https://your-domain.com | grep -iE 'referrer-policy|permissions-policy' and confirm exact directive casing and absence of duplicate headers.
Apache
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"
Security Impact: Apache’s Header always set directive overrides conflicting module outputs, ensuring deterministic policy enforcement. Placing this in <VirtualHost> or .htaccess applies the policy to all routed subdirectories.
Verification: Run apachectl -S to verify virtual host context, then test with curl -I -H "Host: your-domain.com" localhost to confirm header propagation.
Cloudflare Workers
addEventListener("fetch", event => {
event.respondWith(
fetch(event.request).then(response => {
const newHeaders = new Headers(response.headers);
newHeaders.set("Referrer-Policy", "strict-origin-when-cross-origin");
newHeaders.set("Permissions-Policy", "geolocation=(), camera=()");
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders
});
})
);
});
Security Impact: Edge-level injection guarantees policy enforcement before traffic reaches origin servers, mitigating origin misconfigurations and reducing latency. Modifying headers post-fetch preserves upstream cache keys while applying security controls.
Verification: Deploy via wrangler deploy, then inspect the cf-ray header alongside security headers using curl -sI https://your-domain.com. Verify the worker route matches the intended path pattern.
Express.js (Helmet)
const helmet = require("helmet");
app.use(helmet.referrerPolicy({ policy: "strict-origin-when-cross-origin" }));
app.use(helmet.permissionsPolicy({
features: { geolocation: ["none"], camera: ["none"], microphone: ["none"] }
}));
Security Impact: Helmet normalizes header casing and prevents duplicate declarations. Using ["none"] maps to the standard () denial syntax, ensuring framework-level consistency with browser specifications.
Verification: Start the server and run curl -sI http://localhost:3000. Confirm 200 OK status and validate that no legacy Feature-Policy headers are emitted alongside the modern directive.
Compatibility & Trade-off Matrix:
strict-origin-when-cross-origin: Preserves analytics attribution and SEO tracking while blocking full-path leakage to cross-origin HTTPS targets.no-referrer: Maximum privacy posture; breaks affiliate tracking, third-party analytics, and CORS preflight debugging.unsafe-url: Legacy compatibility only; exposes full referrer on downgrade to HTTP and violates modern privacy standards.Permissions-Policy: Requires Chromium 89+, Firefox 95+, Safari 15.4+. Legacy clients silently ignore the header, necessitating fallback CSP or server-side API restrictions.
Diagnostic Workflows & Header Validation
Validation requires verifying header presence, directive parsing accuracy, and cross-origin behavior under both secure and insecure transport. Integrating transport-layer controls ensures headers are only evaluated over encrypted channels, aligning with best practices detailed in HTTP Strict Transport Security (HSTS) Deep Dive.
Diagnostic Execution Steps:
- CLI Header Extraction:
curl -sI https://target-domain.com | grep -iE 'referrer-policy|permissions-policy' - Browser DevTools Inspection: Navigate to
Application → Security → View origin headers; cross-reference withNetwork → Headers → Response Headersto verify directive values. - Referrer Stripping Test: Embed a test page on an HTTP third-party domain. Navigate from your HTTPS origin to the HTTP target and inspect
document.referrerin the target console. Expect""orhttps://target-domain.com/depending on policy. - Feature Denial Test: Execute
navigator.geolocation.getCurrentPosition()inside a restricted cross-origin iframe. Expect aSecurityError: Permission deniedor silent failure in the console. - CI/CD Pipeline Integration: Automate validation using
lighthouse(--only-categories=best-practices) or thesecurityheaders.comAPI. Fail builds on missing or malformed directives.
Common Misconfigurations & Resolution Matrix
Misconfigured directives frequently result in broken third-party integrations or silent header stripping by intermediary proxies. Troubleshooting requires isolating origin responses from edge transformations and validating directive syntax against W3C specifications.
| Symptom | Root Cause | Resolution |
|---|---|---|
| Header absent in production responses | CDN edge cache serving stale assets without header passthrough | Enable Cache-Control: no-cache for header injection rules; verify origin always directive and purge edge cache. |
Permissions-Policy syntax error in console |
Using legacy allow syntax or unsupported feature identifiers |
Migrate to feature=() format; consult MDN for valid feature tokens and ensure no trailing commas. |
| Analytics/affiliate tracking broken | Overly restrictive no-referrer policy stripping UTM parameters |
Revert to strict-origin-when-cross-origin or implement server-side attribution fallback via session cookies. |
| Multiple conflicting header declarations | Application framework and reverse proxy both emitting headers | Consolidate header injection at a single layer (preferably edge/CDN); disable framework defaults (helmet or equivalent). |