Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getHighEntropyValues should not return all values, only values that have been opted in #151

Open
othermaciej opened this issue Nov 2, 2020 · 14 comments

Comments

@othermaciej
Copy link

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.

@jonarnes
Copy link

jonarnes commented Nov 2, 2020

Related to #37 The js API does not require opt-in.

@othermaciej othermaciej changed the title Does getHighEntropyValues return all values, or only values that have been opted in? getHighEntropyValues should not return all values, only values that have been opted in Nov 2, 2020
@othermaciej
Copy link
Author

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).

@yoavweiss
Copy link
Collaborator

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.

@othermaciej
Copy link
Author

I don't think Permission Policy is an adequate solution for the case of third-party scripts running in the main frame:

  1. Permission Policy would presumably be default-enabled for the main frame, so significant fingerprinting surface would be exposed to these scripts for any site that doesn't go out of it's way to opt out. There is a pervasive phenomenon on the web today of first parties embedding third party scripts without full knowledge or awareness of the scripts privacy impact. It's not reasonable to expect sites to go out of their way to opt into privacy protections against these scripts. The average news site doesn't have that expertise. The default should be privacy.
  2. 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. So even opt-in protection would not be effective. (Unless I'm missing something and Permission Policy does have a way to do this.) That said, even if I'm wrong on this, I think Permission Policy is inadequate due to point 1.

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.

@clelland
Copy link

clelland commented Nov 4, 2020

  1. 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 w3c/webappsec-permissions-policy#378, although there is a possible path back to that with w3c/webappsec-permissions-policy#408 now.

  1. 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.

@miketaylr
Copy link
Collaborator

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?

miketaylr added a commit to miketaylr/ua-client-hints that referenced this issue Dec 16, 2020
miketaylr added a commit that referenced this issue Jan 8, 2021
Issue #151 - Make it possible to reject a promise from getHighEntropyValues
@miketaylr
Copy link
Collaborator

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 #151 (comment). cc @othermaciej

@othermaciej
Copy link
Author

@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.

@miketaylr
Copy link
Collaborator

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.

@yoavweiss
Copy link
Collaborator

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.

@miketaylr
Copy link
Collaborator

Any thoughts on #151 (comment), @othermaciej?

@othermaciej
Copy link
Author

@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.

@miketaylr
Copy link
Collaborator

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).

@clelland
Copy link

Yep, that's a thing now -- w3c/webappsec-permissions-policy#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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants