WICG / client-hints-infrastructure

Specification for the Client Hints infrastructure - privacy preserving proactive content negotiation
https://wicg.github.io/client-hints-infrastructure
Other
61 stars 26 forks source link

Access Client Hints can't be delegated to the document opting-in using `<meta>` tags #134

Open jonarnes opened 1 year ago

jonarnes commented 1 year ago

Scenario:

The document root / of an origin needs access to Client Hints, and the only option is to use <meta> tags like <meta http-equiv="delegate-ch" content=""> and/or <meta http-equiv="accept-ch" content="">.

The problem is that these two <meta> tags, in any combination, will not make the client hints available to the document opting in (/), but only to subsequent requests initiated by that document.

Issues:

  1. <meta http-equiv="accept-ch"> is semantically incorrect because opting-in using the meta tag does not have the same behaviour as the accept-ch HTTP header. Opting in using HTTP headers, will make client hints available on the next navigation (reload) to the document root /.
  2. <meta http-equiv="delegate-ch" content=""> alone will delegate the desired hints to the specified parties, including the origin (!), even without explicit opt-in using <meta http-equiv="accept-ch" content="">. This might be intended by design, but what is then the purpose of <meta http-equiv="accept-ch" content="">?
  3. It is reasonable to expect that <meta http-equiv="accept-ch" content=""> will add hints to the Accept-CH cache, which it don't.

Here's a glitch demoing the scenarios.

Proposed solution:

Because the content of http-equiv="delegate-ch" should be parsed like a policy directive it is reasonable to suggest that <meta http-equiv="delegate-ch" content=""> would be able to delegate access to the hints using 'self' so that the document root / can interact with the client hints: <meta http-equiv="delegate-ch" content="dpr 'self'"> It may also reasonable to require an explicit opt-in in addition to the delegation using <meta http-equiv="accept-ch" content="">, but to be semantically correct and offer the expected behaviour, the hints should be added to the Accept-CH cache. Delegating to 'self' should probably have no effect if the origin didn't opt-in with http-equiv="accept-ch" first.

Related issue: 108

arichiv commented 1 year ago

The supported solutions for client hint request/delegation are the Accept-CH http header and Permissions-Policy http header -and/or- the Delegate-CH meta tag. The Accept-CH meta tag is legacy syntax.

There is overlap in the ability of the Accept-CH http header and Delegate-CH meta tag to request client hints be sent.

There is also overlap in the ability of the Permissions-Policy http header and the Delegate-CH meta tag to delegate client hints to specific origins.

It's true that we don't support , self, src, or none in the Delegate-CH meta tag (as noted in 12.4.1). We could consider expanding the syntax, but it shouldn't be necessary to delegate to 'self' at all given 'self' is the default permissions policy for all client hints (except for those with a default of ). The list of origins in the Delegate-CH meta tag adds but does not detract from the current permissions policy. This is different from overriding the permissions policy (which is what the HTTP header does). You can see that 'self' is in fact auto-delegated to by virtue of it being the default permission by setting your demo's Delegate-CH to sec-ch-ua; sec-ch-ua-arch; sec-ch-ua-bitness; sec-ch-ua-full-version; sec-ch-ua-full-version-list; sec-ch-ua-mobile; sec-ch-ua-model; sec-ch-ua-platform; sec-ch-ua-platform-version; device-memory; dpr; width; viewport-width; save-data; downlink; ect; rtt.

A more straightforward way to describe the interaction between Client Hints and Permissions is to say that for a Client Hint to be included in a given request, (1) the hint must have been requested by the top level frame and (2) the permission for the hint must be delegated to that origin. The Accept-CH and Permissions-Policy http headers split the task of (1) and (2) between them, while the Delegate-CH meta tag can do (1) and a limited version of (2).

I'm not sure that we should add hints to the cache due to their inclusion in a Delegate-CH tag. Specifically, while the Accept-CH http header always overwrites the cache when set, the Delegate-CH meta tag permits only expansion. We don't require that every client hint listed in the Accept-CH http header is re-listed in the Delegate-CH meta tag, but we would have to if we wanted the Delegate-CH tag to impact the cache (otherwise those who could only use the Delegate-CH meta tag wouldn't have a clean way to clear the cache).

kamermans commented 1 year ago

Thanks @arichiv, so my takeaway is that it is impossible for the requested page to get the client hints using meta tags alone. The only way to achieve this is via the accept-ch HTTP header. Is this correct (and intentional)?

arichiv commented 1 year ago

The only way for client hints (other than the default 4 low entropy hints) to be included on navigation for a top-level frame is to use both Accept-CH and Critical-CH (Accept-CH is sufficient only for hints to be included on subsequent visits).

This is by design, but I'm open to discussing other approaches.

kamermans commented 1 year ago

Ok, thanks for the confirmation, that has been my observation as well. I'll have a chat with @jonarnes to see if this worth arguing for.

jonarnes commented 1 year ago

Thank you @arichiv @kamermans for your insights. Makes sense.

However, I still feel that it is a fair ask to have client hints opted in for (regardless if it's done with http-equiv="delegate-ch" or http-equiv="accept-ch"`) available when the top-level frame is loaded the 2nd time. ...which means that the hints must be added to the accept-ch cache.

At this stage I realise I should have had some known logos and real-world use-cases, but I don't. However, use-cases are similar as when the critical-ch hint was born. For the long tail of the web, I would argue that adding hints to accept-ch cache from meta tags would ease the transition to user-agent client hints for example.

We don't require that every client hint listed in the Accept-CH http header is re-listed in the Delegate-CH meta tag, but we would have to if we wanted the Delegate-CH tag to impact the cache (otherwise those who could only use the Delegate-CH meta tag wouldn't have a clean way to clear the cache).

I agree that using headers is the best approach, but this case is about those who for various reasons are unable to add headers. I'd say that the relationship between the Accept-CH http header and Delegate-CH tag is artificial. Either one would use the headers 100% or one would use the markup approach.

arichiv commented 1 year ago

I can see the utility, but it is a significant change in the meaning of the tag (allowing it to overwrite the Client Hints cache). That said, I can't imagine many are using both HTML and HTTP delegation of hints (beyond demo websites) and depending on the lack of caching for HTML delegation. I'll have to study that first to be sure though. @yoavweiss for thoughts on the idea.

yoavweiss commented 1 year ago

AFAIR, the main reason for only allowing header-based Accept-CH to impact the CH cache is to avoid an inherent architecture where untrusted renderers are setting the CH cache in ways that impact future renderers on the same origin.

Allowing HTML-based cache influence would result in a less-secure architecture. At the same time, it will enable sites that cannot set headers to set non-critical hints for future navigation requests. When we made the call, the risk of allowing this seemed to outweigh the benefits.

miketaylr commented 1 year ago

this case is about those who for various reasons are unable to add headers

@jonarnes if you have a concrete use case in mind, would you mind sharing here, including how common you believe this is?

For people who are unable to deploy headers, do you know what platforms they might be on? I'm aware that some WordPress setups don't typically allow header editing, but it is possible to author plugins to do so (e.g., https://wordpress.org/plugins/csp-manager/)

jonarnes commented 1 year ago

Well, I wish I had stats on this, but the statement is only a "qualified assumption". There is still a divide between frontend and backend. Whatever can't be done in js, css or html, is backend. In an era where the pendulum is maxing out on the fat-client-side anything backend is in the blind spot. It is true that you can manage headers through plugins, and even on static site hosters like Netlify et. al., but the challenge is the cross platform compatibility. Moving from Netlify to Cloudflare Pages will break the header setup, and possibly forgotten because it's not handled in js/css/html. More important than a concrete use case maybe is the inconsistent behaviour between <meta http-equiv="accept-ch"> and <meta http-equiv="accept-ch" content=""> which adds to the developer confusion.

miketaylr commented 1 year ago

I see, so the concern is that for client-heavy apps "SPAs", etc. that might be hosted on static sites, there's a risk that people forget to migrate properly if they switch providers.

It does seem like the pendulum is starting to swing back the other way (at least on my Twitter feed), with folks re-discovering that servers also exist (i.e., Next, Remix) and provide options for setting custom headers.