Good walkthrough of an underused header. One boundary worth stating explicitly, because it decides whether the control actually protects you: Permissions-Policy governs features per browsing context, so it is excellent at constraining what an embedded iframe may do, but it does nothing against a third-party script you loaded into the top document itself. A tag-manager script running in your own origin can still call getUserMedia regardless of the header; the only thing that mitigates that is script governance (SRI where possible, CSP script-src with strict allowlists, and ideally not giving the third party your origin at all). The other pairing that matters: Permissions-Policy and CSP frame-ancestors answer different questions - the first says "what may my embedded content do," the second says "who may embed me" - and you need both, because an attacker who can iframe your authenticated page has a clickjacking channel that camera/mic/geo lockdowns never touch. Two headers, two threat models.
Thanks Damiano, the frame-ancestors pairing is a great addition: Permissions-Policy limits what my embeds can do, frame-ancestors limits who can embed me, and clickjacking lives entirely in the second one. One nuance on the top-document point: a deny-all like camera=() does apply to the top document too, so even a tag-manager script running in your origin gets a policy error on getUserMedia. Where you're right is the moment you allow (self): that script runs as you, so the header can't tell it apart from your own code. At that point script governance (strict CSP script-src, SRI, keeping third parties off your origin) is the only real control. Two headers, two threat models, plus script hygiene for whatever you allow as self.
Good walkthrough of an underused header. One boundary worth stating explicitly, because it decides whether the control actually protects you: Permissions-Policy governs features per browsing context, so it is excellent at constraining what an embedded iframe may do, but it does nothing against a third-party script you loaded into the top document itself. A tag-manager script running in your own origin can still call getUserMedia regardless of the header; the only thing that mitigates that is script governance (SRI where possible, CSP script-src with strict allowlists, and ideally not giving the third party your origin at all). The other pairing that matters: Permissions-Policy and CSP frame-ancestors answer different questions - the first says "what may my embedded content do," the second says "who may embed me" - and you need both, because an attacker who can iframe your authenticated page has a clickjacking channel that camera/mic/geo lockdowns never touch. Two headers, two threat models.