Open Seirdy opened 2 years ago
I don't think this will work because what is supported per year will be user agent dependent (and unlikely to get agreement). It's also a bit of a footgun, because it doesn't actually tell you what is included per year.
You can also use a generation or version number, but I also think it needs an easy way to „deny all“ for hardening headers.
For reference, this kind of issue is typically solved by using a library/middleware which automatically inserts headers. Basically, the developer just installs a library like Helmet for NodeJS Express or Talisman for Flask, and then the library can make educated guesses regarding the desired headers. These libraries/middlewares can provide an option to disable all permissions by default and permit only specified exceptions. For example, Flask supports both Permissions-Policy and its predecessor Feature-Policy at the same time. This approach would have the least compatibility fallout because then each project explicitly decides when to upgrade its middleware dependency and is actually in control of its own headers.
On Sun, Sep 04, 2022 at 02:34:22PM -0700, Anton Bershanskiy wrote:
For reference, this kind of issue is typically solved by using a library/middleware which automatically inserts headers.
This does not solve the issue of header bloat. Right now, the header is over 1kb and will only get larger with time.
-- Seirdy (https://seirdy.one)
This does not solve the issue of header bloat. Right now, the header is over 1kb and will only get larger with time.
Is header bloat that big of a problem in practice? I believe that if the HTTP header remains the same within the entire HTTP/2 or HTTP/3 session, then the header will be sent only once and saved by the server, and then reused afterward. This article gives an example for HTTP/2. HTTP/1.1 does not explicitly support header compression, but it does support compressing the entire message, so headers are somewhat compressed too.
On Mon, Sep 05, 2022 at 03:12:23PM -0700, Anton Bershanskiy wrote:
Is header bloat that big of a problem in practice? I believe that if the HTTP header remains the same within the entire HTTP/2 or HTTP/3 session, then the header will be sent only once and saved by the server, and then reused afterward.
HPACK and QPACK are optional features that not all browsers and clients support. libcurl, for instance, supports neither. Moreover, this also requires HTTP/2 and HTTP/3 support; a quick look through a typical access log for a public service should reveal that HTTP/1.1 responses are still quite common.
This only applies to repeat visits. Initial visits will still have significant header bloat, making it difficult to initiate a connection/download for all blocking resources in the first round-trip.
This only addresses the performance issues of header bloat, but there's also the secondary issue of complexity (e.g. requiring middleware). As this header will only get bigger every time a new sensitive feature is standardized, we need some sort of forwards-compatible mechanism to keep it under control. A biennial meeting to figure out the set of implemented directives seemed like the most obvious choice to me.
-- Seirdy (https://seirdy.one)
Well,
Another aspect, a deny-list approach is generally frowned upon in the security community. Why start it here. Yes the web might be of open nature, thats why a single header "lock everything i will request the stuff i need" sounds like a good compromise (even when it has the problem to define what "everything" means :)
Well, this is not a security feature. It's primarily a feature to delegate authority. It also allows imposing restrictions on oneself, but that is not the primary motivation. And safelisting would make it rather problematic to extend it going forward. E.g., it would make it hard-if-not-impossible to introduce a permission for <input type=file>
as sites using safelisting might expect that to continue working.
One place where having a broad safelisting-based approach is when creating gateways to other systems such that their content is supposed be rendered at least to some degree but 1) it's potentially arbitrary and 2) you are comfortable occasionally breaking things that could be used for Bad Purposes™. I have the concern for (older-style, but that have to stick around) IPFS gateways. I could totally live with Permissions-Policy: anything-browsers-think-can-be-misused=()
.
The problem is that we don't know who will build upon it and wether or not they will hold it correctly.
I think we should be comparing wins relative to HTTP/3 with the latest in header compression.
@annevk I'm a browser "with the latest in header compression", fetching a web page. I race a TCP-based ALPN run against an HTTPS record lookup (Chromium's behavior). Either the HTTP/2 ALPN wins the race, or the HTTPS DNS record does not exist. Both are, and will remain, common scenarios. So I fetch the page over HTTP/2. This is the initial request; dynamic HPACK hasn't kicked in. I download a 1.56kb HTTP response header:
Permissions-Policy: accelerometer=(),ambient-light-sensor=(),attribution-reporting=(),autoplay=(),battery=(),bluetooth=(),browsing-topics=(),camera=(),ch-device-memory=(),ch-downlink=(),ch-dpr=(),ch-ect=(),ch-lang=(),ch-partitioned-cookies=(),ch-prefers-color-scheme=(),ch-prefers-reduced-motion=(),ch-rtt=(),ch-save-data=(),ch-ua=(),ch-ua-arch=(),ch-ua-bitness=(),ch-ua-full=(),ch-ua-full-version=(),ch-ua-full-version-list=(),ch-ua-mobile=(),ch-ua-model=(),ch-ua-platform=(),ch-ua-platform-version=(),ch-ua-reduced=(),ch-ua-wow64=(),ch-viewport-height=(),ch-viewport-width=(),ch-width=(),clipboard-read=(),clipboard-write=(),compute-pressure=(),conversion-measurement=(),cross-origin-isolated=(),direct-sockets=(),display-capture=(),document-domain=(),encrypted-media=(),execution-while-not-rendered=(),execution-while-out-of-viewport=(),focus-without-user-activation=(),fullscreen=(),gamepad=(),geolocation=(),gyroscope=(),hid=(),identity-credentials-get=(),idle-detection=(),interest-cohort=(),join-ad-interest-group=(),keyboard-map=(),local-fonts=(),magnetometer=(),microphone=(),midi=(),navigation-override=(),otp-credentials=(),payment=(),picture-in-picture=(),private-state-token-issuance=(),private-state-token-redemption=(),publickey-credentials-get=(),run-ad-auction=(),screen-wake-lock=(),serial=(),shared-autofill=(),smart-card=(),speaker-selection=(),storage-access=(),storage-access-api=(),sync-script=(),sync-xhr=(),trust-token-redemption=(),unload=(),usb=(),vertical-scroll=(),wake-lock=(),web-share=(),window-placement=(),xr-spatial-tracking=()
Now, through Alt-Svc
or an HTTPS record lookup, I discover HTTP/3 support. I download a render-blocking asset over an upgraded HTTP/3 connection. This is the first HTTP/3 request; dynamic QPACK compression hasn't kicked in. I download a 1.56kb HTTP response header:
Permissions-Policy: accelerometer=(),ambient-light-sensor=(),attribution-reporting=(),autoplay=(),battery=(),bluetooth=(),browsing-topics=(),camera=(),ch-device-memory=(),ch-downlink=(),ch-dpr=(),ch-ect=(),ch-lang=(),ch-partitioned-cookies=(),ch-prefers-color-scheme=(),ch-prefers-reduced-motion=(),ch-rtt=(),ch-save-data=(),ch-ua=(),ch-ua-arch=(),ch-ua-bitness=(),ch-ua-full=(),ch-ua-full-version=(),ch-ua-full-version-list=(),ch-ua-mobile=(),ch-ua-model=(),ch-ua-platform=(),ch-ua-platform-version=(),ch-ua-reduced=(),ch-ua-wow64=(),ch-viewport-height=(),ch-viewport-width=(),ch-width=(),clipboard-read=(),clipboard-write=(),compute-pressure=(),conversion-measurement=(),cross-origin-isolated=(),direct-sockets=(),display-capture=(),document-domain=(),encrypted-media=(),execution-while-not-rendered=(),execution-while-out-of-viewport=(),focus-without-user-activation=(),fullscreen=(),gamepad=(),geolocation=(),gyroscope=(),hid=(),identity-credentials-get=(),idle-detection=(),interest-cohort=(),join-ad-interest-group=(),keyboard-map=(),local-fonts=(),magnetometer=(),microphone=(),midi=(),navigation-override=(),otp-credentials=(),payment=(),picture-in-picture=(),private-state-token-issuance=(),private-state-token-redemption=(),publickey-credentials-get=(),run-ad-auction=(),screen-wake-lock=(),serial=(),shared-autofill=(),smart-card=(),speaker-selection=(),storage-access=(),storage-access-api=(),sync-script=(),sync-xhr=(),trust-token-redemption=(),unload=(),usb=(),vertical-scroll=(),wake-lock=(),web-share=(),window-placement=(),xr-spatial-tracking=()
I've burned 3.2kb on what may be a small page, for one header. Rendering hasn't started yet. Add headers this is meant to complement (Document-Policy
, CSP, etc.), all with an expanding list of directives, and header size becomes a problem. This is before we consider clients that lack HPACK/QPACK support (nearly all HTTP libraries I've used), or resources on other domains.
CSP had a good solution: grouping the fetch directives that existed at the time under default-src
. Similarly, we can try defining a large set of permissions to group under one directive. Every few years, we could add a new meta-directive to keep forward- and backward-compatibility while also keeping header size from growing out of control.
POSSE note from https://seirdy.one/notes/2023/08/03/permissions-policy-header-bloat/
Just over a year later, a full invocation of the header that includes all Chromium permissions has grown from 1.56kb to 1.83kb:
Permissions-Policy: accelerometer=(),all-screens-capture=(),ambient-light-sensor=(),attribution-reporting=(),autoplay=(),bluetooth=(),browsing-topics=(),camera=(),captured-surface-control=(),ch-dpr=(),ch-device-memory=(),ch-downlink=(),ch-ect=(),ch-prefers-color-scheme=(),ch-prefers-reduced-motion=(),ch-prefers-reduced-transparency=(),ch-rtt=(),ch-save-data=(),ch-ua=(),ch-ua-arch=(),ch-ua-bitness=(),ch-ua-platform=(),ch-ua-model=(),ch-ua-mobile=(),ch-ua-form-factors=(),ch-ua-full-version=(),ch-ua-full-version-list=(),ch-ua-platform-version=(),ch-ua-wow64=(),ch-viewport-height=(),ch-viewport-width=(),ch-width=(),clipboard-read=(),clipboard-write=(),compute-pressure=(),controlled-frame=(),cross-origin-isolated=(),deferred-fetch=(),digital-credentials-get=(),direct-sockets=(),direct-sockets-private=(),display-capture=(),document-domain=(),encrypted-media=(),execution-while-out-of-viewport=(),execution-while-not-rendered=(),fenced-unpartitioned-storage-read=(),focus-without-user-activation=(),fullscreen=(),frobulate=(),gamepad=(),geolocation=(),gyroscope=(),hid=(),identity-credentials-get=(),idle-detection=(),interest-cohort=(),join-ad-interest-group=(),keyboard-map=(),local-fonts=(),magnetometer=(),media-playback-while-not-visible=(),microphone=(),midi=(),otp-credentials=(),payment=(),picture-in-picture=(),popins=(),private-aggregation=(),private-state-token-issuance=(),private-state-token-redemption=(),publickey-credentials-create=(),publickey-credentials-get=(),run-ad-auction=(),screen-wake-lock=(),serial=(),shared-autofill=(),shared-storage=(),shared-storage-select-url=(),smart-card=(),speaker-selection=(),storage-access=(),sub-apps=(),sync-xhr=(),unload=(),usb=(),usb-unrestricted=(),vertical-scroll=(),web-app-installation=(),web-printing=(),web-share=(),window-management=(),xr-spatial-tracking=()
My point about gradual growth of this header is evidenced by the the history of the Chromium DevTools generated protocol.ts
file.
A comprehensive Permissions-Policy header to "opt out of everything":
Others have suggested an
all
permission; however, this isn't feasible because the meaning ofall
will change every time a new permission is added. Sites usingall
will not be forward-compatible.My proposal: combine all these into a special permission called
2022
. In 2023, new permissions might crop up; they won't be included in the2022
permission. At the end of 2023, a new permission called2023
can be created that will include2022
and all permissions added in 2023.The equivalent header content: