Permissions-Policy (früher Feature-Policy) ist ein Header, mit dem die App den Browser anweist, welche Web-APIs überhaupt benutzt werden dürfen — und ob eingebettete iframes diese APIs ebenfalls nutzen dürfen. Klassische Anwendungs-Fälle: Kamera/Mikrofon nur in der eigenen App, Geolocation gar nicht, Payment-Request-API nur in Same-Origin-Frames. Wichtige Härtungs-Schicht gegen XSS-Eskalation, Drittpartei-Tracking und Browser-Feature-Missbrauch.

Was die Policy macht

Ohne Permissions-Policy darf jede eingebettete Ressource (Skript, iframe) prinzipiell die Browser-API-Permissions deiner Seite nutzen — wenn der User zustimmt. Wenn ein XSS-Bug es in die Seite schafft oder ein Third-Party-Tag-Manager unkontrolliert agiert, kann das Skript:

  • Den User um Kamera-Zugriff bitten.
  • Geolocation abfragen.
  • Payment-Request initiieren.
  • USB-/Bluetooth-Device-Verbindung anfordern.

Mit Permissions-Policy kann die App sagen: „Diese API ist hier komplett aus. Niemand kann sie nutzen, auch wenn der User zustimmt."

Beispiel:

HTTP permissions-policy-strict.txt
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()

Alle vier APIs sind komplett deaktiviert. Der Browser blockt Requests gar nicht erst — keine Popup-Anfrage, kein Permission-Dialog.

Syntax

Format: <feature>=<allowlist> mit Komma-getrennten Features.

Allowlist-Werte:

  • () — niemand darf (auch nicht eigene Origin).
  • (self) — nur eigene Origin darf.
  • (self "https://partner.example.com") — eigene plus genannte Origins.
  • * — alle (alle eingebetteten iframes dürfen).

Beispiele:

HTTP permissions-policy-examples.txt
# Kamera nur in eigener Origin, Mikrofon gar nicht, Geo nur für Partner
Permissions-Policy: camera=(self), microphone=(), geolocation=(self "https://maps.partner.com")

# Payment-Request nur self
Permissions-Policy: payment=(self)

# Alle modernen APIs explizit deaktivieren (Maximal-Restriktiv)
Permissions-Policy: accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(),
                    camera=(), display-capture=(), document-domain=(), encrypted-media=(),
                    execution-while-not-rendered=(), execution-while-out-of-viewport=(),
                    fullscreen=(self), geolocation=(), gyroscope=(), magnetometer=(),
                    microphone=(), midi=(), navigation-override=(), payment=(),
                    picture-in-picture=(), publickey-credentials-get=(self),
                    screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(),
                    xr-spatial-tracking=()

Wichtige Features

FeatureBeschreibungEmpfehlung
cameragetUserMedia für Video() oder (self)
microphonegetUserMedia für Audio() oder (self)
geolocationnavigator.geolocation() außer wenn gebraucht
paymentPayment Request API(self) für Checkout-Seiten, sonst ()
usbWebUSB() außer Hardware-Apps
bluetoothWeb Bluetooth() außer Hardware-Apps
autoplayAuto-play für Video/Audio(self) oder ()
fullscreenrequestFullscreen(self) für Video-Apps
display-captureScreen-Sharing() außer Konferenz-Apps
encrypted-mediaDRM (Widevine, etc.)(self) für Streaming
publickey-credentials-getWebAuthn(self) für Auth
accelerometer, gyroscopeMotion-Sensors() außer Game/AR
document-domaindocument.domain-Setting (deprecated)() immer
web-shareWeb Share API(self)
clipboard-read, clipboard-writeClipboard-Zugriff(self)

Default-Empfehlung für Standard-App ohne spezielle Hardware:

HTTP permissions-policy-standard.txt
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(self),
                    usb=(), bluetooth=(), accelerometer=(), gyroscope=(),
                    magnetometer=(), display-capture=()

iframe-Permissions

Bei eingebetteten iframes greift zusätzlich das allow-Attribut. Es kann nur Features aktivieren, die die Parent-Policy erlaubt — niemals erweitern.

HTML iframe-allow-attribute.html
<!-- Parent-Header: Permissions-Policy: camera=(self "https://video.example.com") -->

<!-- Iframe darf Kamera nutzen, weil Parent-Policy es erlaubt UND iframe-Allow es setzt -->
<iframe src="https://video.example.com/call"
        allow="camera; microphone"></iframe>

<!-- Iframe darf Kamera NICHT nutzen, weil iframe-Allow es nicht setzt -->
<iframe src="https://video.example.com/other"></iframe>

<!-- Iframe darf Kamera NICHT nutzen, weil Parent-Policy diese Origin nicht erlaubt -->
<iframe src="https://fremde.example/call"
        allow="camera"></iframe>

Wichtig: das iframe-allow-Attribut kann nur einschränken oder gleich-bleiben, niemals erweitern. Auch wenn ein iframe allow="camera" setzt, muss die Parent-Policy es ebenfalls erlauben.

Migration von Feature-Policy

Feature-Policy war der alte Name (2018–2020). Wurde umbenannt zu Permissions-Policy (2020). Syntax leicht anders:

Plain feature-vs-permissions-syntax.txt
# Alt (Feature-Policy)
Feature-Policy: camera 'self'; microphone 'none'

# Neu (Permissions-Policy)
Permissions-Policy: camera=(self), microphone=()

Unterschiede:

  • Trennzeichen: Feature-Policy benutzt ; zwischen Features, Permissions-Policy ,.
  • Allowlist-Syntax: Feature-Policy quotiert ('self', 'none'), Permissions-Policy nutzt Klammern ((self), ()).
  • 'none' wird zu () — leere Klammer-Liste.

Stand 2026: alle aktuellen Browser unterstützen Permissions-Policy. Feature-Policy ist deprecated, kann aber als Backup parallel gesetzt werden für sehr alte Browser (selten relevant).

Konfiguration

nginx:

Nginx nginx-permissions-policy.conf
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(self), usb=()" always;

Express (Helmet):

JavaScript express-permissions-policy.js
// Helmet hat noch keine direkte API für Permissions-Policy (Stand 2026)
// Header manuell setzen
app.use((req, res, next) => {
  res.setHeader('Permissions-Policy',
    'camera=(), microphone=(), geolocation=(), payment=(self), usb=()');
  next();
});

Caddy:

Caddy caddy-permissions-policy
header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(self), usb=()"

Reporting

Permissions-Policy unterstützt die Reporting-API:

HTTP permissions-policy-reporting.txt
Reporting-Endpoints: pp-endpoint="https://reports.example.com/permissions"
Permissions-Policy: camera=(self), microphone=()
Permissions-Policy-Report-Only: camera=(), microphone=();report-to=pp-endpoint

Wenn ein Skript versucht, getUserMedia für Kamera aufzurufen, sendet der Browser einen Violation-Report — auch im Report-Only-Modus.

Use-Case: strikte Policy mit Report-Only ausrollen, beobachten, ob unerwartete Aufrufe kommen, dann auf Enforce umschalten.

Browser-Support und Quirks

Stand 2026:

  • Chrome, Edge, Brave: voller Support seit Chrome 88 (2021).
  • Firefox: Support seit Firefox 99 (2022) für die meisten Features.
  • Safari: voller Support seit Safari 16.4 (2023).

Vorsicht: nicht jedes Feature ist in jedem Browser implementiert. Permissions-Policy Features List hat die aktuelle Implementierungs-Matrix.

Quirk: Manche Features sind fetch-Metadata-relevant. sync-xhr=() deaktiviert synchrone XHRs — kann legacy Code brechen.

Quirk: document-domain=() deaktiviert das Setzen von document.domain. Bei manchen Legacy-Single-Sign-On-Setups problematisch.

Interessantes

Default ist Browser-spezifisch, nicht überall "aus"

Wer keinen Permissions-Policy-Header setzt, hat die Browser-Defaults — die sind nicht überall maximal restriktiv. Klassisch: Kamera/Mic in Top-Frame erlaubt (mit User-Prompt), in Cross-Origin-iframes meist nicht. Explizit setzen ist klarer als auf Defaults zu vertrauen.

Permissions-Policy schützt nicht vor lokalem JS

Wenn Same-Origin-JS auf der Seite die Kamera anfordert und Policy camera=(self) ist, wird der User-Prompt gezeigt. Die Policy regelt, wo der API-Zugriff überhaupt möglich ist, nicht ob der User zustimmen muss. Plus XSS-Schutz: bei XSS-Lücke in self-Origin könnte Angreifer-Code die Kamera anfordern — Permission-Schicht hilft nur, wenn Policy camera=() ist.

Iframe-Embeds mit Restriktion

Wer YouTube-Embeds erlaubt, gibt mit <iframe allow="encrypted-media; picture-in-picture"> die nötigen Features. Pro Embed-Type minimal halten — nur die wirklich benötigten Features durchschleusen.

Sandbox + Permissions-Policy als Defense-in-Depth

<iframe sandbox> ist die ältere Schicht, blockt JS, Forms, Popups. allow="..." mit Permissions-Policy ist die Feature-Schicht. Beide kombinierbar: maximal restriktives iframe = Sandbox plus leere Permissions.

Web-Apps für Hardware brauchen explizite Permissions

Apps für 3D-Drucker, Mikrocontroller, USB-Sensoren brauchen usb=(self). Bluetooth-Apps brauchen bluetooth=(self). Default-Block ist Standard, nur dort wo gebraucht aktivieren.

Permissions-Policy für Cookies (`Permissions-Policy: cookie-store=()`?)

Die Spec hat experimentelle Features auch für Cookies, Storage, etc. — Stand 2026 nicht produktiv. Browser-Verhalten zu Storage-Beschränkungen wird klassisch über andere Header gesteuert (CHIPS für Cookies, Storage-Access-API).

Lockdown-Mode (iOS) und Permissions-Policy

Apple's Lockdown-Mode deaktiviert viele Features auf Browser-Ebene (Just-In-Time-Compilation, WebGL, einige APIs). Permissions-Policy operiert pro Seite — Lockdown-Mode ist User-System-Wide. Beide Schichten ergänzen sich: System-Lockdown ist Defense für High-Risk-User, Policy ist Defense für jede App.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Secure Headers & Cookies

Zur Übersicht