Open Bug 1319168 Opened 4 years ago Updated 8 days ago

Implement externally_connectable from a website

Categories

(WebExtensions :: Compatibility, enhancement, P3)

enhancement

Tracking

(Not tracked)

People

(Reporter: rpl, Unassigned)

References

(Blocks 1 open bug)

Details

(Whiteboard: [triaged][chrome])

The browser.runtime's onConnectExternal/onMessageExternal API methods, besides being used to make a WebExtension "externally_connectable" from a different addon (which was already being discussed and tracked by Bug 1258360), can be used to make a WebExtension "externally_connectable" from a website.

This issue will focus on the "externally_connectable from a website" part of onConnectExternal/onMessageExternal (evaluating what its behavior should be, discuss any concerns related to possible threats, and track its implementation).
See Also: → 1258360
It interesting to notice that being able to be "externally connectable from a website" seems to be much more requested and much more used by addon developers, probably one reason is that it would let them to integrate with a website without injecting a content script in every webpage of that website (or of any website, which means that the description of the addon permissions that is going to be presented to the user will not contain a "can read everything on websiteX", but something that is going to sound less scary, like "websites can send messages to this addon")

For the same reason, it seems that an addon developer can prefer to let any website to be able to connect to their addon (which seems to not being currently possible in chrome: https://github.com/MetaMask/metamask-plugin/issues/813#issuecomment-261363677)
Some initial thoughts about some potential concerns/threats that we may want to discuss before providing this feature:

- should/can we prevent the content script of a different addon from using the messaging API provided to the website from an "externally_connectable addon"?

- should/can we prevent code that is running in the webpage but is not originated by the same origin (e.g. an injected script tag from a different domain) from using the messaging API provided to the webside from an "externally_connectable addon"?
Whiteboard: [advisory-group]
Adding note to myself to find some numbers of how much this is used in Chrome add-ons to communicate from a webpage to an add-on.
Flags: needinfo?(amckay)
A quick scan of 57804 Chrome extensions shows:

1837 externally_connectable                        // number of extensions with this manifest key
  22 externally_connectable_accepts_tls_channel_id
   1 externally_connectable_ids average            // average number of ids per definition
 114 externally_connectable_ids
  75 externally_connectable_ids_*                  // number of extensions that use *
   2 externally_connectable_matches average        // average number of matches per definition
1812 externally_connectable_matches                // extensions that define a 
   2 externally_connectable_matches_*              // number of extensions that use <all_urls>
Flags: needinfo?(amckay)
Flags: needinfo?(kmaglione+bmo)
Whiteboard: [advisory-group]
Whiteboard: triaged
Hi There, sorry for not chiming in sooner, as the discussion was somewhat spurred by my web extension! (Thanks Luca! We've been busy, as I'm sure you have been!)

We just finally started identifying our pain points with Chrome's Extension API, and started a discussion with them, and so it seemed like a good time to warm up the iron on this side too.

I think Luca's security concerns are valid, but could be addressed with security policies, for example a safe and very restrictive policy might require that injected APIs of that type are not able to send messages on behalf of the page, or issue http/fetch requests from the site's origin domain, or maybe are only able receive function calls from the page, and can only communicate with their own Extension's context (although we extensions would love to talk to each other too).

If our injected API was merely able to pass its messages to the content script or background script, and return the responses, that alone would be incredibly powerful, granting the ability to add new APIs to the browser via nothing but an extension.

This is exactly what MetaMask is today, but it's not easy. We're injecting "web3", which is an immature, and very rough API for interacting with some peer-to-peer networks like the Ethereum blockchain. MetaMask is allowing developers to experiment with patterns of developing on these peer to peer networks, and the experience we've gotten from even our rough current version has spurred some great discussion & design around the next iteration of the API.

Andy: If you wanted even more interesting numbers, check how many users each of those extensions has. Just out of curiosity, I don't actually think this is a very common pattern yet.
Priority: -- → P3
Andy: From your quick scan of the 1837 Google Chrome extensions that contains "externally_connectable", around 175 of those extensions are mine.

As this is currently a P3, I'll assume this implementation will not land into 57 release and will follow something like this as an alternative : https://stackoverflow.com/questions/10526995/can-a-site-invoke-a-browser-extension/10527809#10527809
Correct, this won't land in 57.
Blocks: 1392434
Duplicate of this bug: 1406624
Update: since Andy posted comment 4 one year ago, the number of Chrome extensions that use externally_connectable has grown to 3,735 out of 82,659 extensions, or 4.52%, up from 3.18% in comment 4.
Severity: normal → enhancement
Adding to browser compatibility pages https://github.com/mdn/browser-compat-data/pull/2329
Product: Toolkit → WebExtensions
Flags: needinfo?(kmaglione+bmo)
Whiteboard: triaged → [triaged][chrome]
I am developing a website that communicates with an extension that is implemented to extend the functionality of this website. The website and the extension sometimes need to exchange sensitive data. Chrome extensions can be made 'externally_connectable'. Using this mechanism I can assure that from the point of view of my website I am communicating with the right extension by using the id of the extension.

In Firefox however I need to fall back on CustomEvent or window.postMessage which technically works ok, but does not guarantee me that I am communicating with the right extension. By using CustomEvent or postMessage any other extension can possibly eavesdrop on the [website - extension] connection.

I'd like to add to this discussion that adding the 'externally_connectable' feature is significantly adding to the secureness of this kind of extensions.
Flags: needinfo?(ddurst)
Flags: needinfo?(ddurst)

The l10n team is evaluating new translation management systems for our vendor pipeline. One of the candidates (Lingotek) has an extension for performing in-context localization on websites. This feature could reduce l10n turnaround time and increase quality of localizations for marketing campaigns on mozilla.org. Shane, how do we go about raising the priority of this bug? What additional information do you need?

Flags: needinfo?(mixedpuppy)

I don't see us being able to fit this in. IIUC This also requires exposing an api to content which will have a very high bar. cc/Philipp to see what he thinks.

Flags: needinfo?(mixedpuppy) → needinfo?(philipp)

My take on the security concerns detailed in comment #2:

  • should/can we prevent the content script of a different addon from using the messaging API provided to the website from an "externally_connectable addon"? Chrome's implementation won't allow to send messages to extensions from content scripts, so if implemented the same way, this shouldn't be an issue.

  • should/can we prevent code that is running in the webpage but is not originated by the same origin (e.g. an injected script tag from a different domain) from using the messaging API provided to the webside from an "externally_connectable addon"? This is indeed possible in Chrome's current implementation and sender.origin returns the host's page, rather than the origin of the injected script, so you can't even check that. My hunch here is that it's up to the host page to protect itself from cross-site scripting and shouldn't be relevant here. I'd argue that externally_connectable is still more secure than the known workarounds, as reported in comment #11.

Regarding collecting numbers of how many extensions are using this, please note that the absence of "externally_connectable" from a codebase might just mean that people are using workarounds in order to support Firefox and don't bother maintaining two separate implementations. If it wasn't for the added security of externally_connectable, I'd consider that myself.

So, please, consider adding this. 🙏

externally_connectable does not offer the security guarantees that comment 18 and comment 11 describe. The answer to comment 2 is "No, we cannot":

  • Extensions with the right permissions can run scripts in web pages, and that code is indistinguishable from pre-existing code in the web page. Therefore any attempt to prevent other extensions from communicating with the web page is futile. Another result is that extensions that listen to messages from externally_connectable cannot tell with certainty that the message originates from a specific website (opposed to a different extension).

  • There is no meaningful distinction between inline scripts, same-origin scripts and cross-origin scripts - ultimately they all run in the same context.

Thanks for chiming in, Rob Wu. Feel free to push back, but for what I can tell, if my extension doesn't allow other extensions to send me messages, they can't send them even from content scripts.

With the following manifest.json, only *somesite.com can send me messages:

"externally_connectable": {
    "matches": ["https://*.somesite.com/*"]
}

To allow another extension (even from content scripts), I would have to add the ids array. Without it, the message doesn't hit my extension.

This is even clearer when you think that when I allow an extension, its message has a sender.id that identifies the extension even from a content script.

So, to the best of my knowledge, external message sources are whitelisted on the receiving end. Everything else won't reach it.

My use case is similar to comment #11. I want to communicate data from the website to the extension. With externally_connectable:

  • I can be sure that the message comes indeed from the expected website
  • an injected script wouldn't have that data anyway (it lives in the memory of the website, it's not stored in localStorage or cookies)

On the other hand, using a postMessage or a CustomEvent an injected script could eavesdrop and steal it.

From the MDN page:

Externally connectable allows extension developer to control which other extensions and web pages can communicate with this extension via runtime.connect() and runtime.sendMessage() message passing. If externally_connectable is not specified, all extensions can communicate with each other but not with web pages.

I was able to confirm this in my tests.

Now, sending messages from the extension to webpages or content scripts is another matter altogether and there you don't have any guarantee. From Chrome's developer page:

Assume any data sent to the content script might leak to the web page. Limit the scope of privileged actions that can be triggered by messages received from content scripts.

But this is not my use case and it doesn't invalidate my assumptions above.

If you know otherwise, please let me know. I am trying to implement the best solution for my users and, for what I can tell, externally_connectable is the better option (much simpler and cleaner, and more secure).

The lack of this option in Firefox will force me to maintain two different solutions and decrease security and privacy for my Firefox users.

(In reply to Emanuele Feliziani from comment #20)

I can confirm the issue described by Emanuele also applies to my extension that is ought to collaborate with a specific website. We recently had our extensions pentested and Firefox implementation was labeled again as less secure because of the clumsy implementation of the communication mechanism (by dispatching new CustomEvent(...))

(In reply to Emanuele Feliziani from comment #20)

Thanks for chiming in, Rob Wu. Feel free to push back, but for what I can tell, if my extension doesn't allow other extensions to send me messages, they can't send them even from content scripts.

With the following manifest.json, only *somesite.com can send me messages:

This is not the case. Extensions with host permissions can impersonate web pages (e.g. via content scripts or by rewriting HTTP responses with the webRequest API).

My use case is similar to comment #11. I want to communicate data from the website to the extension. With externally_connectable:

  • I can be sure that the message comes indeed from the expected website

correction: the expected website plus any other extension that has host permissions for that website.

  • an injected script wouldn't have that data anyway (it lives in the memory of the website, it's not stored in localStorage or cookies)

I don't understand what you mean by this.

On the other hand, using a postMessage or a CustomEvent an injected script could eavesdrop and steal it.

This is no different from the case with externally_connectable. Another extension could patch a web page's chrome.runtime.sendMessage method (or other APIs / scripts in the web page) to eavesdrop on messages.

From the MDN page:

Externally connectable allows extension developer to control which other extensions and web pages can communicate with this extension via runtime.connect() and runtime.sendMessage() message passing. If externally_connectable is not specified, all extensions can communicate with each other but not with web pages.

I was able to confirm this in my tests.

Note that this is Firefox's current behavior - without support for externally_connectable, only extensions can communicate with each other. Firefox does not add a chrome.runtime.sendMessage method to web pages.

Now, sending messages from the extension to webpages or content scripts is another matter altogether and there you don't have any guarantee. From Chrome's developer page:

Assume any data sent to the content script might leak to the web page. Limit the scope of privileged actions that can be triggered by messages received from content scripts.

But this is not my use case and it doesn't invalidate my assumptions above.

I think that you've misread the message of the documentation. This warning about extension -> web page does not mean that the other way around is secure. The broader context of the cited documentation is about compromised renderer processes (=the OS process that hosts the web page), with the threat being other (web) content hosted in the same process. The same principle applies to the scenario that I sketched before: when an extension is able to inject code in a web page, then that extension is able to forge and eavesdrop messages to/from the web page.

If you know otherwise, please let me know. I am trying to implement the best solution for my users and, for what I can tell, externally_connectable is the better option (much simpler and cleaner, and more secure).

The lack of this option in Firefox will force me to maintain two different solutions and decrease security and privacy for my Firefox users.

(In reply to timboektoe from comment #21)

(In reply to Emanuele Feliziani from comment #20)

I can confirm the issue described by Emanuele also applies to my extension that is ought to collaborate with a specific website. We recently had our extensions pentested and Firefox implementation was labeled again as less secure because of the clumsy implementation of the communication mechanism (by dispatching new CustomEvent(...))

Under the threat model of malicious extensions having been installed, externally_connectable (+web->extension messaging) is not more secure than CustomEvent (+inter-extension messaging). To emphasize: if your user has a malicious extension, then there is no reliable way for web pages to prevent tampering from that other extension. Consequently, extensions have to assume that any communication with a web page could have been tainted by other extensions.

Thanks for clarifying, Rob Wu. Now I have a better understanding of the issue. A script with full privileges can inject a script and thus appear as the webpage itself.

If I wanted to prevent this from happening:

  1. Could I use CSP to prevent script from other domains to be injected? Would this block extension-generated script tags or those are exempted?
  2. Could I use CSP hashes to prevent an extension from tampering with my original script, as explained here?

an injected script wouldn't have that data anyway (it lives in the memory of the website, it's not stored in localStorage or cookies)
I don't understand what you mean by this.

I mean that this data is known by my application, the third party can't know that. Even if it could forge a message, it would simply be an invalid message. Not much harm done. What I want to avoid is for the third-party to intercept my legitimate communication to my extension.

If this security advantage boils down to nothing, there is still the convenience advantage. The workarounds are rather clumsy and hacky.

Anyway, thanks for taking the time to explain things in detail. Much appreciated.

(In reply to Emanuele Feliziani from comment #23)

Thanks for clarifying, Rob Wu. Now I have a better understanding of the issue. A script with full privileges can inject a script and thus appear as the webpage itself.

If I wanted to prevent this from happening:

  1. Could I use CSP to prevent script from other domains to be injected?

Yes.

Would this block extension-generated script tags or those are exempted?

Extension resources are supposedly exempt from CSP, per https://www.w3.org/TR/CSP3/#extensions . moz-extension: and chrome-extension:-URLs cannot be blocked.

  1. Could I use CSP hashes to prevent an extension from tampering with my original script, as explained here?

Subresource integrity could be used to harden the scripts, but is not an effective defence against all extensions, because extensions can just modify the response to remove the CSP / hashes.

an injected script wouldn't have that data anyway (it lives in the memory of the website, it's not stored in localStorage or cookies)
I don't understand what you mean by this.

I mean that this data is known by my application, the third party can't know that. Even if it could forge a message, it would simply be an invalid message. Not much harm done. What I want to avoid is for the third-party to intercept my legitimate communication to my extension.

That is not possible. Third-party extensions with the right permissions can always intercept legitimate communication with your extension.

Thanka lot for commenting Rob Wu. Really appreciated. You say:

Under the threat model of malicious extensions having been installed, externally_connectable (+web->extension messaging) is not more secure than CustomEvent (+inter-extension messaging). To emphasize: if your user has a malicious extension, then there is no reliable way for web pages to prevent tampering from that other extension. Consequently, extensions have to assume that any communication with a web page could have been tainted by other extensions.

I was thinking that by explicitly adding the extension id as we do for Chrome the webpage is at least sure it is communicating with the right extension. I now realise that another malicious extension can tamper the code of the webpage and manipulate the extensionId by spoofing the extensionId in the js code of the site.

However, in case I encrypt the extensionId on the server side with a private key and decrypt the extensionID within the context of the webpage with a public key, would the solution then not be much more harder to tamper with compared to the CustomEvent approach? Like installing 2 locks on the front door instead of 1.

I was thinking that by explicitly adding the extension id as we do for Chrome the webpage is at least sure it is communicating with the right extension. I now realise that another malicious extension can tamper the code of the webpage and manipulate the extensionId by spoofing the extensionId in the js code of the site.

The malicious extension doesn't need to spoof anything. Anything readable to the web page is also readable to that malicious extension.
At some point, a web page that wishes to send a message to a specific extension has to hand off a plain text extension ID and message to a messaging API (e.g. chrome.runtime.sendMessage or dispatchEvent+CustomEvent). A malicious extension does not even need to put any effort in deciphering the initial input, all it needs to do is to intercept calls to these messaging APIs.

However, in case I encrypt the extensionId on the server side with a private key and decrypt the extensionID within the context of the webpage with a public key, would the solution then not be much more harder to tamper with compared to the CustomEvent approach?

Not at all. This is not having any positive impact on security. On the contrary, the unnecessarily complicated system is more likely going to have implementation errors that degrade the security. If your threat model includes malicious extensions, then you need to accept the risk of other extensions tampering with the content. If you want to completely rule out that possibility, then the core of the functionality should run in an extension page.

stale ni

Severity: normal → S4
Flags: needinfo?(philipp)
You need to log in before you can comment on or make changes to this bug.