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

Make implicit and explicit deny indistinguishable #120

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

bvandersloot-mozilla
Copy link
Collaborator

@bvandersloot-mozilla bvandersloot-mozilla commented Oct 4, 2022

This is accomplished by adding a 2-minute timeout to reject an unfulfilled promise immediately before checking for implicit denies and not rejecting the promise when the implicit or explicit deny occurs.

I am open to hearing better ways to specify a timeout or Promise fulfillment testing.

I am also open to discussion of removing the timeout entirely and allowing the Promise to leak.


Preview | Diff

This is accomplished by adding a 2-minute timeout to reject an unfulfilled promise
immediately before checking for implicit denies and not rejecting the promise
when the implicit or explicit deny occurs.
@annevk
Copy link
Collaborator

annevk commented Oct 7, 2022

If this is accepted we should note in the commit message that it fixes #60.

Copy link
Collaborator

@annevk annevk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the idea here is that we still reject early for state-related reasons, such as there not being user interaction, but the user not answering or dismissing the prompt would result in rejection after two minutes?

This algorithm running while "in parallel" is problematic as you shouldn't (can't) mutate state on the global while "in parallel", but that's probably a follow-up. Most things can probably move to the main thread except for actually asking the user.

storage-access.bs Outdated Show resolved Hide resolved
@@ -244,14 +244,16 @@ To <dfn type="abstract-op">determine if a site has storage access</dfn> with [=p

To <dfn type="abstract-op">determine the storage access policy</dfn> for [=partitioned storage key=] |key| with {{Document}} |doc| and {{Promise}} |p|, run these steps:

1. [=Run steps after a timeout=] given |doc|'s {{Window}} object, `"requestStorageAccess"`, `120000`, and the following steps:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a comment explaining this number. Since it's a number it's not to be wrapped in code.

Similarly, instead of "requestStorageAccess" we want "requestStorageAccess" (quotes outside code).

And we should probably use |doc|'s <a>relevant global object</a> here. (Which we already obtain later, so we should move that step up I suppose.)

Copy link
Collaborator Author

@bvandersloot-mozilla bvandersloot-mozilla Oct 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will clean up the code quotes, rearrange this, and add a comment explaining the magic number.

@bvandersloot-mozilla
Copy link
Collaborator Author

So the idea here is that we still reject early for state-related reasons, such as there not being user interaction, but the user not answering or dismissing the prompt would result in rejection after two minutes?

Yes, exactly.

This algorithm running while "in parallel" is problematic as you shouldn't (can't) mutate state on the global while "in parallel", but that's probably a follow-up. Most things can probably move to the main thread except for actually asking the user.

👍

Copy link
Member

@johannhof johannhof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the idea here is that we still reject early for state-related reasons, such as there not being user interaction, but the user not answering or dismissing the prompt would result in rejection after two minutes?

Yes, exactly.

So what responses does the delay make indistinguishable, then?

It would be great if this change could come with an accompanying description of the threat and how we're mitigating it, e.g. in the Privacy & Security considerations.

1. If |implicitly granted| is true, [=queue a global task=] on the [=permission task source=] given |global| to [=/resolve=] |p|, and return.
1. If |implicitly denied| is true, [=queue a global task=] on the [=permission task source=] given |global| to [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}}, and return |p|.
1. If |implicitly denied| is true, return.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this ever need to return p at all, given that the invoking function owns p? Maybe we should clean that up everywhere.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only place we returned p in this algorithm. I don't believe we need to return it here.

@annevk
Copy link
Collaborator

annevk commented Oct 10, 2022

With this you cannot distinguish between the end user saying "no" and the end user saying nothing.

@bvandersloot-mozilla
Copy link
Collaborator Author

So what responses does the delay make indistinguishable, then?

Anne hit it pretty well in his comment.

It would be great if this change could come with an accompanying description of the threat and how we're mitigating it, e.g. in the Privacy & Security considerations.

Sure, I can add something in a subsection there.

@johannhof
Copy link
Member

I don't think that that's accurate, and we should be precise about it to ensure we hit the goals outlined in #60 (comment):

Implicit deny == the browser decides this third party is not allowed to request storage access and immediately rejects. This could be the result of policy or a user facing feature à la "Don't ask me again."
Explicit deny == the user gets prompted and chooses "Don't allow."

So we want to ensure that sites cannot tell whether users have seen the prompt or whether browser policies restricted it. I think this PR does that by ensuring that implementation defined steps are rejected after the timeout, but I would like to be explicit about which steps we'd like to have covered under the timeout and which are fine to reject immediately.

In fact, I think if we had a way to reach this goal while still letting the site know whether the user has responded or is still pondering that would be beneficial, as this is a major usability issue for well-intentioned sites. We've learned this lesson on Firefox for Notification permission prompts which the user could leave invisibly dangling, so that sites would remain in limbo state after asking for permission. It was not nice. We could consider shortening the timeout to, say, 1 min or 30s to reduce that effect.

@annevk
Copy link
Collaborator

annevk commented Oct 25, 2022

I discussed this further with John and he noted that always delaying the answer by a certain amount of time might leave the user with an unresponsive user interface. You click somewhere, you get a prompt, you reply, and then nothing happens (because the browser delays resolving the promise).

So instead we should probably look into delaying the case where we're not prompting with some randomized amount of time that looks identical to the user replying, but cannot be used for tracking.

@bvandersloot-mozilla
Copy link
Collaborator Author

Waiting on #121

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

Successfully merging this pull request may close these issues.

5 participants