Open othermaciej opened 4 years ago
Related to #37 The js API does not require opt-in.
Updating title based on the clarification from @jonarnes . With that clarification this is a critical issue for Safari; we would not implement UA client hints unless this is addressed. (We otherwise feel most of our critical issues have been resolved).
Bringing back the discussion from webkit-dev (my bad!) to here.
I believe that having per-hint Permission Policy to enable the high entropy bits of the JS API (similar to the policies we use for delegation) will give us the ability to both enable the JS API on specific iframes, or disable it entirely (even for the main frame and any scripts that run in its context).
@clelland to keep me honest.
I prefer to keep the Client Hints opt-in as something that's responsible for the request headers but not more than that, as it would start overlapping with Permission Policy.
The spec does not eve allow denying the request entirely as written. A non-normative Note suggests that is allowed, but I can’t find any step in the algorithm that would ever reject the promise.
That sounds like a bug that we should fix.
I don't think Permission Policy is an adequate solution for the case of third-party scripts running in the main frame:
Thus, it seems to me that Permission Policy is not an adequate solution to this issue.
Permission Policy could be an adequate solution for third-party iframes, assuming the default is no access. However, the spec as written does not have hooks for per-hint Permission Policy, so this proposed solution does not yet exist.
If the JS API was defined to expose only the hints opted into by HTTP, or delegated to the domain of a frame via HTTP, then all of these problems would go away.
- Permission Policy would presumably be default-enabled for the main frame
I believe that is the case, as it's defined today. The main frame has significant access to powerful features that cross-origin content does not. I removed the "default:none" mode from Permissions Policy when I landed https://github.com/w3c/webappsec-permissions-policy/pull/378, although there is a possible path back to that with https://github.com/w3c/webappsec-permissions-policy/issues/408 now.
- I don't think there is a way for the main frame to deny itself a permission in a way that another script running in the main frame is unable to turn back on.
This actually is possible now, and was what motivated the changes above. Now if you use the header to explicitly exclude your own origin, you cannot turn the feature back on, cannot access it through any scripts, and cannot create an iframe with access to it.
One possible solution here is to add a policy like ua-ch-high-entropy-values
which has a default allowlist of self
. That means 3rd party iframes wanting to use the API would require 1st party opt-in via the allow
attribute. And if a site wants to disable the API entirely, for 1st party and 3rd party contexts, that can be done via Permissions-Policy: ua-ch-high-entropy-values=()
.
The browser can still audit callers of getHighEntropyValues()
, and if some policy (ITP, ETP, Privacy Budget, etc.) determines they're doing things that violate said UA policy (independent of 1st or 3rd party context), it can deny future gets by returning a rejected Promise.
But I think I don't understand the threat model described here. Stuff like CSP (connect-src, script-src, etc) and restrictions around 3rd party cookies would be a defense against exfiltrating entropy from 1st to 3rd parties. I'm also not aware of other web platform features that distinguish between 1st and 3rd parties, when embedded in a 1st party context (i.e., <script src>
) (but maybe I'm just not coming up with them off-hand).
@othermaciej could you expand on that so I can better understand? Does the proposed solution here address your concerns?
I've added the ability to reject the promise if one or more values is not allowed by the UA in #163, but would appreciate some more feedback on the proposal in https://github.com/WICG/ua-client-hints/issues/151#issuecomment-744756804. cc @othermaciej
@othermaciej could you expand on that so I can better understand? Does the proposed solution here address your concerns?
@miketaylr I'm not clear on what the proposed solution is. Is it something that already exists in the spec or in a PR? If not, could you explain in more detail? Tentatively, it seems like the solution still allows third-party scripts in the first-party domain to access all high entropy client hints by default, which seems wrong to me. But I'm not clear enough on what's proposed to say for sure.
Apologies that it's unclear. Let me try again.
As the spec currently exists (as opposed to when you filed the issue), if a 1st or 3rd party (in a 1st party context) makes a call to getHighEntropyValues()
, the User-Agent has the option to fulfill, or reject the promise:
https://wicg.github.io/ua-client-hints/#getHighEntropyValues step 2 states:
If the user agent decides one or more values in hints should not be returned, then reject and return p with a "NotAllowedError".
The note just below https://wicg.github.io/ua-client-hints/#interface also states:
Note: The high-entropy portions of the user agent information are retrieved through a Promise, in order to give user agents the opportunity to gate their exposure behind potentially time-consuming checks (e.g. by asking the user for their permission).
(that note should probably get moved closer to the algorithm language)
As the draft spec exists today, browsers have the option to hook UA-specific policies into this API (whether those be Enhanced Tracking Protection, Intelligent Tracking Protection, Privacy Budget, a user-facing browser setting, a permission prompt, etc.) and decide to reject on behalf of their users.
Furthermore, we can add a new Permission Policy for the high entropy bits and if sites choose to set it to the empty set, e.g., ua-ch-high-entropy-values=()
, no origin or party (including 1st party) can access the API. No spec language exists for that yet, but I can write up a PR if folks agree that's reasonable.
Furthermore, we can add a new Permission Policy for the high entropy bits and if sites choose to set it to the empty set, e.g.,
ua-ch-high-entropy-values=()
, no origin or party (including 1st party) can access the API. No spec language exists for that yet, but I can write up a PR if folks agree that's reasonable.
I just wanted to chime in saying that I think this is the right approach for gating 1P access to the JS API on an opt-in, which IIUC is what's desired here. Client hints wasn't built with that threat model in mind, and as such, e.g. enables http-equiv support which would allow scripts running in the 1P context to easily opt-in.
Any thoughts on https://github.com/WICG/ua-client-hints/issues/151#issuecomment-783668130, @othermaciej?
@miketaylr
As the spec currently exists (as opposed to when you filed the issue), if a 1st or 3rd party (in a 1st party context) makes a call to getHighEntropyValues(), the User-Agent has the option to fulfill, or reject the promise:
It's an improvement that denying the request is allowed per spec now.
On the other hand, seems like a potential interop problem if some UAs might always allow and others might always deny. (Doesn't seem possibly to allow only what was requested via the http header due to http-equiv support, as mentioned by @yoavweiss)
Furthermore, we can add a new Permission Policy for the high entropy bits and if sites choose to set it to the empty set, e.g., ua-ch-high-entropy-values=(), no origin or party (including 1st party) can access the API. No spec language exists for that yet, but I can write up a PR if folks agree that's reasonable.
Is it actually possible for a site to set a Permission Policy on itself in such a way that it can't undo it from script? Last I looked at the Permission Policy spec this didn't seem possible but maybe things have changed.
I just wanted to chime in saying that I think this is the right approach for gating 1P access to the JS API on an opt-in, which IIUC is what's desired here. Client hints wasn't built with that threat model in mind, and as such, e.g. enables http-equiv support which would allow scripts running in the 1P context to easily opt-in.
Yes, I can see how http-equiv makes it trivial for 3P scripts to bypass any intended limitation. However, this makes me more, rather than less concerned. The new policy hooks to allow UAs to deny a request does not seem to have room for a UA policy that allows only exactly what was requested via HTTP headers because of this issue, at least not in a straightforward way.
Is it actually possible for a site to set a Permission Policy on itself in such a way that it can't undo it from script? Last I looked at the Permission Policy spec this didn't seem possible but maybe things have changed.
Yes, that's my understanding -- scripts can't unset or modify a policy that's been set. @clelland, can you verify?
The first example in https://w3c.github.io/webappsec-permissions-policy/#examples shows a site disabling fullscreen and geolocation for itself (and any embedded frames).
Yep, that's a thing now -- https://github.com/w3c/webappsec-permissions-policy/issues/357 was the issue for it; it's now the case that if you explicitly set a Permissions-Policy
header that doesn't include your own origin for a specific feature, then that feature is disabled on the page and all of its descendants, and that can't be changed by any actions that page takes.
The definition of
getHighEntropyValues
sounds like it might return all the high entropy values, not just ones that have been opted in. That seems inconsistent with the rest of the spec, which requires prior opt-in. Which is intended?If this exposes all values, including ones that have not been opted in, then I have a strong objection to this requirement, as it would allow third party scripts embedded in the first party context to request all entropy values, which dramatically increases fingerprinting surface that can be exposed without active cooperation of the first party.
A non-normative "Note" says that user-agents may "gate their exposure behind potentially time-consuming checks", but it doesn't give a means to answer some, but not all of the questions, nor is this possibility expressed in the definition of the algorithm for
getHighEntropyValues
, which is written as if the promise always resolves successfully and always returns all values.I would strongly recommend returning only the values that the server has opted into via headers, and also explicitly giving license to UAs to reject the promise, or to fail to fill in some of the values.
Another potential problem: it seems like as written, this measure should work even in third-party iframes that the top level document has not delegated permission to. The Client Hints infrastructure covers delegation and permissions for the HTTP headers, but not, I think, for this JavaScript interface.