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

Add permissions.requestSiteAccess() API proposal #529

Merged
merged 10 commits into from
Mar 21, 2024
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 160 additions & 0 deletions proposals/permissions-requestSiteAccess-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# permissions.showSiteAccessRequest() API

## API Overview

### Background

Extensions can request host permissions via:
- Manifest declared `host_permissions`, granted at install time by default
- Manifest declared `optional_host_permissions`, granted at runtime after a user gesture by `<browser>.permissions.request()`
- Manifest declared `matches` for a `content_script`, granted at install time by default

In some browsers, users can withhold host permissions causing the extension to only have site access when the extension is invoked. For example, in Chrome a user can set an extension to run only "on click". When the extension is clicked, it gains site access to the tab's main frame origin (effectively acting like [`activeTab` permission](https://developer.chrome.com/docs/extensions/develop/concepts/activeTab#what-activeTab-allows).

Each browser may decide how it signals the user when an extension is requesting access (e.g through the extensions menu). However, there is no way for an extension to explicitly signal at runtime it’s requesting site access after it was withheld without a user gesture and a heavy-weight permission dialog.


### Objective

Allow the extension to show site access requests at runtime without any user gesture in a less-obtrusive way than with `permissions.request()`. This can be done with a new API that:

- Applies to a specific tab or document id
- Doesn’t need to be made inside the handler for a user action
- Shows the request in the UI, handled differently by each browser. See more in [UI Elements and User-Visible Effects](#ui-elements-and-user-visible\-effects) section
- When accepted, grants always access to the site’s top origin
- Resets the request on cross-origin navigation

#### Use Cases
Copy link
Member

Choose a reason for hiding this comment

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

Overall, this is a confusing API to developers, and it would be better if there were more concrete use cases. What value can this API bring to extensions? Is there any relevant data for browsers' "site access" function? Are users satisfied with "site access" function, or are users encountering any problems? The above questions are just to avoid developing an API that no one uses.

Copy link
Member

Choose a reason for hiding this comment

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

I can share a bit of extra context here. We are still looking at possible permission UI changes like the ones mentioned at I/O. This doesn't include changing any defaults, but we do still expect more users to experiment with different access settings. It feels like putting APIs in place first is better than adding them retrospectively, which would imply a gap between when the UI launches and when APIs to adapt to them are available.

Copy link

@fregante fregante Jan 26, 2024

Choose a reason for hiding this comment

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

This feels like it belongs in a manifest rather than as an API. I think extensions fall into these categories:

  • on demand: just take an action when invoked (activeTab)
  • for specific websites: think "Reddit Enhancement Suite" or "Refined GitHub", that have a pre-set list of domains to run on
  • for all websites: think content blockers, Grammarly

Between these there are a couple more cases:

  • A: pre-set list, but more can be added
  • B: all websites, but some can be disabled

"A" can be achieved via .request() when the user desires; "B" is covered by Chrome's current UI.

I'm not sure where requestSiteAccess falls on this spectrum.

Once I specify optional_host_permissions: all_urls, the browser should already:

  • Show "This extension is available on this website"
  • Allow toggling the permission

In your proposed new UI, it looks like this could be covered already: just show the extension on the list, but disabled.


As a user, I wish that I had a way to enable the type A on more websites in a consistent way.

As a developer, I wish I didn't have to manually handle content script registration and permission requests.

Copy link
Member

Choose a reason for hiding this comment

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

I definitely agree that not having to manually handle this would be nice and think that'd be interesting to experiment with. For some of your cases I agree that might work.

I do think there is another case to the ones you mentioned. Consider a shopping extension that can only offer coupons on certain sites, and it downloads this list remotely. It specifies <all_urls>, so can run everywhere, but a user explicitly revokes its access from all pages. It would be nice if the extension had a way to less intrusively flag "I checked my list and I can run here, you might want to add me back on this domain".

Choose a reason for hiding this comment

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

It would be nice if the extension had a way to less intrusively flag "I checked my list and I can run here, you might want to add me back on this domain".

Why isn't the list in the manifest?

But still from a manifest standpoint, what is the point of host_permissions if they can be disabled? It sounds like we need:

  • compatible host list: all_urls
  • suggested host list: eBay, amazon, etc

To fit into this proposal:

  • compatible host list: host_permissions
  • suggested host lists: a new preferred_hosts

The drawback is that extension developers have to submit a new extension to update the list, but the advantages are that the browser has total control on how/when to show it, and the developer doesn't have to deal with issues like "when and how often can I requestSiteAccess?"

Copy link
Member

Choose a reason for hiding this comment

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

Ah, the wording may have been slightly confusing there. It would often be used to grant access to content scripts. The API itself would definitely be available in background contexts.

An example use case in Chrome would be something like this:

  • An extension requests <all_urls> in host_permissions
  • The user changes the site access to "on click" or "on specific sites" (note that they can do this even if the host permissions weren't specified as optional)
  • In the background, the extension realises it can inject a useful content script (for example, it notices a tabs.onUpdated event and the tab has a URL it has special logic for)
  • The extension would then prompt for access to that tab ID

Does that make sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's take a step back and see current behavior (updated proposal Background section too)
Please, feel free to correct me on other browsers behavior since I'm not fully familiar!

  • An extension can request host permissions. At installation, they are granted (Chrome, Firefox) or withheld (Safari) by default
  • An extension can request optional host permissions, that can be granted after a user action
  • In Chrome and Safari, extension site access can be withheld
  • In Chrome, withholding an extension site access means the extension is set to run "on click". When the extension is clicked (either the pinned action or through the extensions menu) it gets access to run on the origin top frame until cross-origin navigation.
  • permissions.request() allows an extension to request site access after a user action

Let's have an example now (similar to Oliver's on previous comment). In Chrome:

  1. An extension requests <all_urls> in host_permissions
  2. User navigates to site.com
  3. The user changes the site.com site access to "on click"
  4. User navigates to site.com/useful
  5. In the background, the extension realizes it can inject a useful content script on site.com/useful

User can realize the extension wants to run by opening the extensions menu. However, there is no way for the extension to say "I really want to run on this specific page" without user action. Yes, it could work with a declarative list. However, we have listed our reason why we prefer not to (updated proposal's Other Alternatives Considered section)

@hanguokai
As for the issue of extension requesting host_permissions that are not in the tab top frame origin. At least on Chrome, this is the actual behavior of granting site access to an extension. Granting site access to a host (either by activeTab or allowing site access "on site" / "on all sites") grants only to that host, meaning the tab's top frame origin. This new API is following the same, so it wouldn't fix that issue.

@fregante

it asks for further host permissions or it asks for permanent permission to the current context

Extension cannot ask further host permissions. It would ask for host_permissions that were withheld by the user

As a user, if a the browser asks for a permission for a third domain, I don’t think I'd accept without further explanation, especially if I explicitly blocked the extension on that domain before.

Browser cannot ask permission for a third domain

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for your further explanation. Now I have a clearer understanding of the intent of this API. But in my opinion, this is still a controversial API.

How to use this API

  1. In the background, the extension realizes it can inject a useful content script on site.com/useful

Can you further explain what developers are supposed to do here?

Because this API is bound to browser tabs, I think developers need to use tabs.onCreated/onUpdated/onReplaced events to detect tab's top frame changes, and must need the "tabs" permission to get tab.url and/or tab.title. In these events, developers are facing two possible situations: 1. already has the host permission and already inject content scripts 2. don't has the host permission and no injected content scripts. Therefore, developers first need to determine whether has the host permission. If no host permission, at this stage, extensions only know tab's url and title, and don't know the page's content since it has not inject a content script. At this point, developers can only rely on these two pieces of information (url and title) to determine whether to apply for the site permission, i.e. call permissions.requestSiteAccess().

Here, without knowing the content of the page, it may be difficult for developers to determine whether it strongly requires the site access. In the case of shopping, I only know the URL and title, I may can't know what product is being sold on the current page, so I can't tell if there is a coupon for that product.

The model itself

In Chrome, withholding an extension site access means the extension is set to run "on click". When the extension is clicked (either the pinned action or through the extensions menu) it gets access to run on the origin top frame until cross-origin navigation.

I rarely use this feature. I also rarely hear users use this feature. So I want to repeat the question I asked at the very beginning: Is there any relevant data about this function? Are users satisfied with this function, or are users encountering any problems at present?

I think the model itself is flawed, it only fits a small subset of real use cases, and a lot of use cases (perhaps 50% or more) don't fit the model (We have already given examples before).

Experiment with this API without standardization

In my opinion, there are still doubts about the actual value of this API for developers and users. I think Chrome can first experiment with this API (for a year) to see the actual effect. Tell developers explicitly that this is an experimental API and that it may be modified incompatibly or even removed in the future. This way, an experimental API does not require the agreement from other browsers. For example, let developers apply an Origin Trial token to experiment this API (see #454).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How to use this API

Correct, those are the two scenarios although I would phrase them slightly differently (to match terminology):
a. Extension request host permission and it's granted by the user. Extension has injected scripts
b. Extension request host permission and it's withheld by the user. Extension ha no injected scripts

On scenario a., you are right. Extension does not know the page content. Extension has access to:

  • permissions.getAll() returns the active permissions. Extension can know if a host permission is not in the returned permissions origin
  • tabs.get() returns tab URL, title, ...
  • other APIs like windows.getCurrent() to get more information

Here, without knowing the content of the page, it may be difficult for developers to determine whether it strongly requires the site access. In the case of shopping, I only know the URL and title, I may can't know what product is being sold on the current page, so I can't tell if there is a coupon for that product.

Currently, the only way for developers to determine whether it requires site access is to check if it has active permission for the page. This provides a way to show a visible request when this happens.

Yes, it's probably not sufficient to know given a tab id / document id that the extension "strongly needs access to the site". However, what would be another way to determine that? (if host permissions are withheld, extension doesn't have access to the page). By looking at document id / tab id, extension can get more information than if it just gave a declarative list of URLs (as explained in previous point).

The model itself

I rarely use this feature. I also rarely hear users use this feature.

User withholding host permissions is not a common practice today. However, we expect that to change in the future because:

  • Browsers are exploring withholding permissions at installation. Safari already does that, and I believe Firefox is exploring that in MV3. Chrome is exploring that too
  • At least in Chrome, it will be more accessible for the user to withhold host permission in the extensions menu (that means, setting the extension to run "on click")

Is there any relevant data about this function? Are users satisfied with this function, or are users encountering any problems at present?

We did user research on showing a request in the Chrome toolbar every time a extension wants site access to a withhold host permission. Users found it to be too nosy, and not always helpful. However, they mentioned that the request is relevant then it provides value.
Thus, we drafted this proposal to show the request only when the extension requests to show it. Yes, it can be always but at least it gives the option to the extension to have it be more relevant and the browser to better control it.

I think the model itself is flawed, it only fits a small subset of real use cases, and a lot of use cases (perhaps 50% or more) don't fit the model (We have already given examples before).

Agree, the real use cases subset is small now. However, we expect this to increase as mentioned on the first point on this section.

Experiment with this API without standardization

Do other browsers see a potential path forward for this API? Otherwise, Chrome can continue without standardization

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Proposal was updated to include url so it can fit other browsers. Browser can add requirements on which parameters must be provided. For example, Chrome will require tabId or documentId to be provided and url can be added on top of that.


An extension requested access to a site but the user withheld its access and forgot about it. Extension wants to signal it needs site access to execute without user action. For example, “shopping” extension wants to show site access when user navigates to “amazon.com” and access was withheld.

### Consumers

Extensions that want to signal the user when they need site access.

## Implementation

### API Schema

```
// Shows a site access request. Request will only be signaled to the user if
EmiliaPaz marked this conversation as resolved.
Show resolved Hide resolved
// extension can be granted access to site in the request (i.e., one specified
// in host_permissions, optional_host_permissions, a content script match
// pattern, or an applicable activeTab site).
// Resolves whether the request is valid and is signaled to the user.
EmiliaPaz marked this conversation as resolved.
Show resolved Hide resolved
Promise<bool> <browser>.permissions.showSiteAccessRequest(
EmiliaPaz marked this conversation as resolved.
Show resolved Hide resolved
// The id of the tab where site access requests can be shown. If provided,
// the request is shown on the specified tab and is removed when the tab
// navigates to a new origin.
// Chrome requires either this or `documentId` to be specified.
number?: tabId,
// The id of a document where site access requests can be shown. Must be
// the top-level document within a tab. If provided, the request is shown on
// the tab of the specified document and is removed when the document
// navigates to a new origin.
// Chrome requires either this or `tabId` to be specified.
string?: documentId,
// The URL pattern where site access requests can be shown. If provided,
// site access requests will only be shown on URLs that match this pattern.
// Browsers may require different levels of specificity.
string?: url
EmiliaPaz marked this conversation as resolved.
Show resolved Hide resolved
callback?: function,
);

// Removes a site access request, if existent.
// Resolves whther the request was removed.
Promise<bool> <browser>.permissions.removeSiteAccessRequest(
EmiliaPaz marked this conversation as resolved.
Show resolved Hide resolved
// The id of the tab where site access request will be removed.
// Chrome requires either this or `documentId` to be specified.
number?: tabId,
// The id of a document where site access request will be removed.
// Chrome requires either this or `tabId` to be specified.
string?: documentId,
// The URL pattern where site access request will be removed.
string?: url
EmiliaPaz marked this conversation as resolved.
Show resolved Hide resolved
callback?: function,
);
```

Note: We don’t support iframes since they are not included in the runtime host permissions UI.

### New Permissions
| Permission Added | Proposed Warning |
| ---------------- | -------------------------------------------------------- |
| N/A | Permission’s API is used to “request declared optional permissions at run time rather than install time, so users understand why the permissions are needed and grant only those that are necessary” (according to documentation). The goal of this new method is for the extension to request site access, which is effectively a permission. We can adjust the description to not be restricted just to optional permissions. |
Copy link
Member

Choose a reason for hiding this comment

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

"permission's API" -> "permissions API"? (There is no extension API called "permission")

Do we want this API to be unconditionally available to all extensions? I think that it would be good to put this behind a permission or manifest key that doesn't require a warning, because that makes it easier to statically determine whether extensions use this feature.

Otherwise it is rather unfeasible to enforce the policy of responsible use, and the API becomes useless as a signal to request attention from the user.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"permission's API" -> "permissions API"? (There is no extension API called "permission")

Correct, it was a typo.

Do we want this API to be unconditionally available to all extensions? ...

I'm not sure if I follow this.

  • Permissions API does not require a warning. Calling permissions.request(x) could trigger a warning iff x has a warning.
  • Permissions API doesn't require an entry in manifest permissions
    Enforcing responsible use does not have to be connected to whether the permission 'Permissions API' is present, instead each browser can determine it (e.g limit to a number of calls if user has dismissed it on a site). Why would it be useless?

Copy link
Member

Choose a reason for hiding this comment

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

Do we want this API to be unconditionally available to all extensions? ...

I'm not sure if I follow this.

I was wondering whether we should add a new permission or manifest key, because requiring either means that the set of extensions that can abuse it drops from all to only a few (that use it), which can enable reviewers in the extension store to pay closer attention to verify whether the use of the API is acceptable.

  • Permissions API does not require a warning. Calling permissions.request(x) could trigger a warning iff x has a warning.

By "that doesn't require a warning" I was emphasizing that the new permission/manifest key does not need a permission message. There are several APIs that are behind permissions that do not trigger a warning message in the prompt. This could be another one.

Enforcing responsible use does not have to be connected to whether the permission 'Permissions API' is present, instead each browser can determine it (e.g limit to a number of calls if user has dismissed it on a site). Why would it be useless?

So from the browser perspective it doesn't matter, since the enforcement is based on the use of the API. In the broader ecosystem, it may be desirable to know whether an API may be used without running all extensions (e.g. for review, auditing, capability analysis, etc.). The manifest.json file is a machine-readable format, and a new permission (or manifest key) enables automatic scanning.


#### Other Alternatives considered

- Action API is used to control the extension’s button in the browser’s toolbar. It’s exposed if the extension includes the "action" key in the manifest. This is troublesome since an extension could not have an action, but still want to show site access requests. We should not limit requests for extensions with actions
Copy link
Member

Choose a reason for hiding this comment

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

What are use cases benefiting from the proposed API without action button? The current bullet point makes it sound like there are extensions without button that need the API. But the typical use case, i.e. shopping extension, would usually have a button.

An API to draw attention to the action button would be more generic and enable more use cases beyond just granting access to the top-level origin. It would also enable an extension to offer more context to the permission request.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In Chrome, an extension without an action can request host permissions, which can be withheld. To grant access, the user can click on the extension icon or extension entry in extensions menu. Once the extension has access, the extension is not clickable because it doesn't have an action.

Now, it's unlikely that an extension doesn't specify an action. The majority wants at least to specify the action's "default icon". However, that is not required and there are extensions that don't specify an action and require host permissions. For example, there is an extension that doesn't have an action and injects a script that makes google docs comments wider.

Originally, I was very undecided between permissions vs action. Went with permission for the reason explained, although I can also see argument why it would be better to have it with action

- `permissions.showSiteAccessRequest()` resolves when request is accepted/rejected. An extension could be requesting site access and be granted site access through another mechanism (e.g changing site access in the extensions menu). We would either return a) true if we consider permissions granted through other mechanisms or b) false, because permission wasn't explicitely granted through the request. a) adds complexity and b) may cause confusion. Thus, we consider better to resolve whether the request is valid, and separately extension can listen whether permission is granted through `permissions.onAdded()`.

### Manifest Changes

None.

### Custom Bindings

None.

### Design and Implementation

#### Persistence

Showing a site access request is never persisted.

#### Alignment with Other Vendors and Action APIs

Each browser can decide how they want to signal the extension site access requests. Browsers may decide to ignore the request (e.g due to noisiness, or if an extension has no visible UI).
On Chrome’s side, we are exploring adding an ‘Allow’ chip in the extensions toolbar.

## User Experience

### UI Elements and User Visible-Effects

Each browser will handle the request as desired. On Chrome’s side, we are considering showing an ‘Allow’ button in the toolbar for these requests. On click, it would grant persistent access to the site’s origin (which can be again withheld by the user)

!["Site access request on Chrome toolbar"](./assets/permissions-requestSiteAccess-chrome-toolbar.png)

We would also provide a way for users to silence the requests for an extension through a setting in the extensions menu and in chrome://extensions.

!["Extensions menu on Chrome"](./assets/permissions-requestSiteAccess-chrome-menu.png)

#### Attribution
The site access request will be attributed directly to the extension.

## Security and Privacy

### Exposed Sensitive Data

This API does not directly expose any sensitive data to the extension. However, it could lead to the extension gaining access to the site.

### Abuse Scenarios

Extensions can enable site access requests on every tab, which could be annoying to the user.

#### Mitigations
Attacks of annoyance are not in our threat model. Extensions can have annoying behaviors, but they risk uninstallation.
On the browser side, we can place some restrictions to limit noisiness:
Rob--W marked this conversation as resolved.
Show resolved Hide resolved
- User can decide whether they want to see site access requests for an specific extension
- We can explore setting a max amount of times the request is shown when the user ignores it.
xeenon marked this conversation as resolved.
Show resolved Hide resolved

### Additional Security Considerations
None

## Alternatives

### Existing Workarounds

Users can change an extension's site access. The only way for extensions to signal the user they want to regain access to the site is through permissions.request() flow which is noisy and requires a user gesture. Otherwise, they rely on the user realizing through the browser UI (e.g extensions menu).

Extensions have no way to signal the user that they want access to the site, and rely on the user realizing through the browser UI (e.g extensions menu)

### Other Alternatives Considered

Specifying URL patterns instead of tabId or documentId in `permissions.requestSiteAccess` or in the extension's manifest. We decided against that because:
- This is designed to be a highly-contextual signal. The extension should do it only if they have strong believe they will provide value to the user on the given page. This should not be a passive, "hey, I think I can do something here", it should be a "hey, you, the user, probably want me to do something here".
- We do not want extensions to simply show a request on every page, and specifying a list of patterns would lend itself to that behavior (even by not allowing broad match patterns).
- It's too close to host permissions themselves. We suspect that the vast majority of extensions would just have the same field match as in their host permissions, since there is no more knowledge at manifest time about why the extension would run on a specific site.

### Open Web API
This is related to the extensions showing site access requests in the browser; it doesn't affect the web.
Loading