EIP-1102: Opt-in provider access

Hi @serso. While the experimental Permissions API initially seemed promising for the purposes of opt-in provider access, key limitations made it less than ideal for this specific use case.

The current Permissions API doesn’t offer the ability to define custom permissions and was only intended to provide a better API to request native browser permissions like “geolocation” or “notification”. Requesting a non-standard permission - like “ethereum” - throws an Error. While dapp browsers could override the permissions.query method to explicitly handle a non-standard “ethereum” permission request, malicious sites could then initiate this non-standard request and know they’re in a dapp browser if no Error is thrown as expected. For example:

The following non-standard permission request will immediately throw:

navigator.permissions.query({ name: 'ethereum' });
// TypeError: The provided value ethereum is not a valid PermissionName.

If dapp browsers override permissions.query to handle “ethereum” requests, it will not immediately throw:

const originalQuery = navigator.permissions.query;
navigator.permissions.query = (query) => {
	if (query.name === 'ethereum') {
		// Handle provider request...
    } else {
		return originalQuery.apply(navigator.permissions, arguments);
    }
};
navigator.permissions.query({ name: 'ethereum' });
// undefined

Because the Permissions API is meant only for predefined permissions and doesn’t (yet) allow for dynamically-defined permissions in a given context, any support at all for a non-standard “ethereum” permission would allow malicious websites to fingerprint and track Ethereum users. The Permissions API also displays a default browser confirmation dialog and doesn’t allow custom confirmation UIs; this limits the type of information that can be presented to the user, but this issue is less important.

Thanks for your comment, let me know if you have any other ideas around this. I agree that the Permissions API would be great to leverage if it was safely usable for non-standard permission types. We’ll continue to monitor it closely for EIP-1102 applicability.

Hi! Nice proposal.

In the proposal it says that:

IF user rejects
IF non-Ethereum environment
    NOOP[4]

I think it would be a good idea to also notify the dapp when the user reject the access, so can detect the case and give a better user feedback.

Also, would it make sense to allow also to request a list of provider details, so you can prompt the user with a wallet selector, then the user chooses his preferred one, so we can request the injection of that particular wallet?

Hi again, also I was wondering why do we need still a global variable ethereum for the new method. Could we just send it as as an argument when the extension triggers the listener?

Another small detail, why the event s called message and then we check for the type ETHEREUM_PROVIDER_SUCCESS?. I think the event name can be an arbitrary name, so it can be more specific. Right? Besides I think message is kind of standard one used for websockets: https://developer.mozilla.org/en-US/docs/Web/Events

WDYT?

1 Like

Thank you for the prompt response, @bitpshr. I’ll bring it up for an [internal] discussion at Opera. Maybe we can help you with the web standards.

2 Likes

Hi @anxolin. Thanks for your comments. Responses below:

I think it would be a good idea to also notify the dapp when the user reject the access, so can detect the case and give a better user feedback.

If dapps were notified when a user rejected provider access, that would expose enough information to allow malicious sites to continue uniquely identifying Ethereum users. For example, if a malicious site initiates a provider request, a user rejects access, and the malicious site is notified, the site now knows the user is an Ethereum user within a dapp browser. Even without provider or account access, a malicious site can still run targeted ad and phishing campaigns simply by knowing a certain IP address is associated with Ethereum use.
I agree that this means dapp UX will need to change, and there are discussions around this very topic. Still, the lack of notification of rejection is a crucial piece to this protocol’s privacy.

Also, would it make sense to allow also to request a list of provider details…

This is a very good idea. The request protocol outlined in this proposal was intentionally designed to be open-ended so that future EIPs can build on it and add new information to the request. @danfinlay had some thoughts around this idea as well, such as requesting a specific account type on a specific network when requesting a provider.

Could we just send it as as an argument when the extension triggers the listener?

Because the provider request protocol leverages window.postMessage, it’s not possible to return the provider object as an argument to the listener. Data sent via window.postMessage is serialized using structured cloning which means the provider object’s functions would be lost. Additionally, while some browsers support a third transfer argument when calling window.postMessage to transfer an object between two windows, browser support is very limited. The only viable way to respond with a full provider (for now) is global injection.

Another small detail, why the event called message and then we check for the type ETHEREUM_PROVIDER_SUCCESS? I think the event name can be an arbitrary name, so it can be more specific.

The protocol makes use of window.postMessage to request a provider, and then listens for the browser to post a message using window.postMessage in response. You bring up an interesting point: the dapp browser could respond by triggering an Ethereum-specific event listener, negating the need to sort through other posted messages based on a type. Part of me likes the symmetry of posting a message, then listening for a posted message, but I will experiment with this today for security.

If we chose to emit an event instead of post a response message after user approval as suggested by @anxolin, dapp browsers could return the provider object to the event listener instead of injecting it globally.

We could go from this:

window.addEventListener('message', ({ data }) => {
    if (data && data.type && data.type === 'ETHEREUM_PROVIDER_SUCCESS') {
        const networkVersion = await window.ethereum.send('net_version', []);
    }
});
window.postMessage({ type: 'ETHEREUM_PROVIDER_REQUEST' }, '*');

To something like this:

window.addEventListener('ethereumprovider', (ethereum) => {
    const networkVersion = await ethereum.send('net_version', []);
});
window.postMessage({ type: 'ETHEREUM_PROVIDER_REQUEST' }, '*');

There wouldn’t be any immediate fingerprinting threat since an event would only be emitted after user approval. What do you think? @anxolin @ryanio @danfinlay @andytudhope @serso @brunobar79

3 Likes

Thanks for including me.

Choosing to emit an event sounds nice. However, I found when coding the implementation in Mist that assigning the provider to window.ethereum is actually nice because on page load if the dapp has previously been authenticated by the user we can load the page with window.ethereum already available without needing another request.

(Also in dev environments you could assume window.ethereum is available without needing to request it.)

With sending an event, we wouldn’t know when in the dapp lifecycle the listener would be set up to know when to emit.

Hi @ryanio, thanks for the feedback. I see your point about auto-injecting window.ethereum for already-approved sites, but this seems like an odd approach for approval caching since it requires dapps to check for window.ethereum before requesting a provider. If a domain has already been approved, rather than injecting window.ethereum automatically, why not still wait for a request, but respond immediately with the provider and show no user dialog? For reference, this is what MetaMask is doing. This approach allows domain caching (meaning users don’t have to constantly re-approve the same dapp) while imposing no additional code on dapps (they still request and add a listener like normal and don’t have to first check for window.ethereum.) It would also be trivial to dispatch an ethereumprovider event on the Window for local testing.

I’m starting to think the ethereumprovider event for success response is a cleaner API, but I’m definitely open to more thoughts.

1 Like

hey guys, I’m Andrey , implementing 1102 in status.im.

Agree with @bitpshr, ethereumprovider event looks cleaner, only one way how to get a provider, and safer, because we don’t post a message for success response. We also implemented in status immediate response if the domain was allowed before.

Do you plan to use CustomEvent? so it will be something like

window.addEventListener('ethereumprovider', (event) => {
    const networkVersion = await event.detail.ethereum.send('net_version', []);
});
4 Likes

So, re: The UX of EIP 1102 - looking for a way to guarantee both user control of privacy and preserve the relative ease of UX available with current Metamask/web3.

My general idea to solve this is basically to find a way to automatically push a refresh and injection of window.ethereum based on an application requesting that, upon install of Metamask, their still-open tab will be able to have a pending request of availability somehow notify the user that they’ve requested access, and then upon access being granted, initiate the reload and window.ethereum injection. Doing so would not expose any user information without their request, while still allowing applications to have relatively seamless UX flow (with the workflow, 1. Request Metamask install and/or request acceptance, 2. Detect Metamask has been opened and accepted). The en-queuing of such a request could be easily accomplished by storage of a specially keyed cookie or localStorage object (e.g., _web3_permission_pending_request). Any risk of accidental acceptance of a website’s pending permission requests could be mitigated by a combiation of (a) only showing requests originating from open tabs or domains, and (b) sufficiently clear UX on the part of the ethereum provider (e.g. Metamask). I strongly encourage an approach like this for keeping UX simplicity.

I like how clean this is, and would be in favor of it provided there are no major downsides we can think of.

I prefer this kind of provider consistency even across dev environments, and I hope @ryanio is open to it.

We are currently implementing/testing provider injection of the GnosisSafe extension (safe.gnosis.io) and often have issues if we have both (the MetaMask extension and the GnosisSafe extension) installed.

Since the GnosisSafe extension already uses a whitelisting approach to check if it should inject a web3 instance, we currently just override any pre-existing web3 instance (in that case the MetaMask instance). This is obviously super ugly and therefore we are quite happy about EIP-1102 and id field :smiley:

I think having an event that returns the provider that should be injected is cleaner than just injecting into a global variable. It would be nice to provide the id of the wallet that emitted the ethereumprovider event.

3 Likes

Cross-posting this here for anyone not following the thread relating to UX around this proposal:

Has there been any discussion about simply using domain whitelisting without user requests and, say, having the Web3 provider toggle on or off for a specific domain based on clicking the navbar menu icon or a context menu therein? You could optionally allow a side route for requests additionally such as via cookie/localStorage which would allow them to exist throughout the lifecycle of the web3 provider extension, as mentioned earlier, but a user could simply be instructed to “click the fox icon if it’s gray” in the Metamask example.

By the way, part of me wonders if a web3 access request system opens the browser up to CPU usage measurement attacks which would negate the purpose of provider obfuscation anyway.

Hi @dustinvs. A true whitelist-based approach introduces its own UX issues by making all dapps inaccessible or unusable by default. Even if users got used to a flow of explicitly approving each new dapp they want to access, it seems odd to ask users to whitelist or approve a new dapp before they even see it. How does a user know they want to whitelist a dapp unless they can view it first? If instead you’re suggesting that users can still view a dapp but the provider would be disabled unless the dapp is explicitly whitelisted, I’m not sure how this UX is any better than EIP-1102 as written: dapps would still need to add fallback UX if they aren’t whitelisted and need to know if they’re in a dapp browser. This also requires users to know each browser-specific mechanism to enable a dapp (e.g. MetaMask may use an icon click, Status may use something different on mobile, etc.)

Allowing dapps to request provider access dynamically and only when needed using a standard DOM API guarantees Ethereum user privacy in a browser-agnostic manner. It’s also been suggested to use this same postMessage-based protocol for parameterized provider requests, such as requesting a specific account type on a specific network, something whitelisting can’t easily support.

The request / response protocol seems extensible, implementable, and safe, but the UX limitation of being unable to detect non-dapp browsers is a win for privacy but a potential loss for UX (at least based on flows that currently rely on global injection.)

It’s not functionally different to, or incompatible with, any of the approaches to requesting access that have been discussed (edit, that is, besides a parameterized one). Consider it a suggestion for implementing user-initiated requests outside of the scope of the dapp. You’re right that it’s not quite a replacement.

1 Like

We are currently using that approach for the Gnosis Safe Extension and in first user testing experienced that this is quite confusing for the user.

UX wise I find this very complicated, since for the user it is easier if they are being told were to click and what to do. If you have to open the browser extension and enable whitelisting it makes it quite a lot harder for the users imo.

What I had in mind was simply the dapp instructing the user to install the extension and then click the icon in the navbar to enable or similar. It’s possibly worse UX than an app-initiated request, especially one based on a mouse/keyboard/touch event, although I suspect that if it’s standardized and intuitive then it’s an issue of training the user to perform a single action for provider injection, as opposed to a different action for each site (if access cannot easily be requested dynamically by the app). If the available options for whatever reason can no longer include an app-initiated request, then something like that becomes a must. The UX issues are of course significantly different on mobile and desktop. Anyway, this may be out of band for the discussion.

The protocol defined in EIP-1102 was originally intended to both standardize the way dapp browsers expose a provider and to do so in a way that makes it impossible to uniquely identify Ethereum users from non-Ethereum users. Despite the technical feasibility of this approach and the “total privacy” it provides, it requires a monumental shift in dapp UX: user on-boarding is made more difficult because dapps can’t detect if a user has a dapp browser installed. We initially thought this level of privacy was clearly ideal, and only after the announcement of our rollout plan did the implied dapp UX receive full scrutiny.

Rethinking total privacy

Taking a step back, the original motivation behind EIP-1102 was to make provider exposure less obtrusive and safer. The proposal satisfies these requirements by requiring explicit user approval before exposing a provider.

But the proposal goes one step further by also removing any fingerprintable API surface from the dapp context. This means that dapps aren’t informed when a user rejects provider access or if they don’t have a dapp browser installed. The idea behind this was to prevent malicious websites from associating IP addresses with Ethereum use and running targeted attacks. But this type of total privacy becomes less important as more browsers with unique user agents add crypto features such as Brave, Opera, or Toshi. Since these browsers are already identifiable by user agent and because they all support Ethereum use either directly or indirectly, malicious sites can already fingerprint many users as potential Ethereum users regardless of EIP-1102 or an injected provider.

Considering these new UX findings and realizing that this type of fingerprint prevention is firmly stopgap in a future of maintstream, crypto-enabled browsers, it feels natural to revisit the goals and approach behind EIP-1102.

An updated proposal

After talking with various community members, weighing informative UX feedback, and realistically analyzing security risks around provider exposure, we drafted updates to EIP-1102 in which DOM environments expose a read-only provider until full provider access is approved by the user. By injecting a restricted provider on page load, dapps can still detect correct browser installation and initiate RPC requests to properly render their UI; by requiring user approval before fully-enabling the provider, users are still protected from unsolicited transactions and unwanted account exposure on untrusted websites.

EIP-1102: Proposed updates (feedback encouraged)

cc @danfinlay @andrey @serso @ryanio @dustinvs @rmeissner @JaceHensley @ricburton @dustinvs

3 Likes