Website to whitelist only specific set of add-on IDs

Hello,
I would like to implement a whitelist on my website to only allow interaction with a specific set of add-ons.
How can a website implement such a thing?
On Chrome, we can use “externally_connectable” which will do just that: The website sends a message by specifying the extension ID, and the extension can receive the message and reply to it. It is useful to whitelist extensions.

On Firefox, the feature is not implemented (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/externally_connectable)

So even though add-ons have unique IDs, I haven’t found a way for a website to identify and whitelist an add-on.

Thank you!

I don’t understand the idea of whitelisting extensions on your website.

I understand that a website may need to exchange messages with a specific extension to add capabilities that Web APIs alone do not provide. So you would whitelist those.

However, what do you intend to be the consequences of having a list? If you are looking for a way to prevent users from running other extensions on your pages, I don’t think there is an API for that.

If I understand your question correctly, you want to send messages from websites to Firefox extensions; and only certain websites are allowed to send messages to your extension.

This should be possible with custom events, content scripts and runtime.sendMessage

https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#using_window.postmessage_in_extensions_non-standard_inline

The reason is:
Our extension provides extra features within our website. Users who wish to use those features should install our extension.
Say another person creates a copy of our extension, and distributes it inside or outside of AMO. Our website should be able to identify that it is not our original extension and not provide those features.

As I mentioned, on Chrome, we can use externally_connectable which does just that. On Firefox however, I don’t know.

I’m not sure how externally_connectable protects against a clone extension. Wouldn’t the clone extension also have your site listed under that same key? I guess this is over my head.

With externally_connectable, the website tries to initiate a connection with a specific extension ID.
Only the extension which has that ID can listen/receive the message.
Since IDs are assigned by the Chrome store and are unique, I think that’s an efficient way to do the whitelisting.
Even if an extension is a copy of my extension, it won’t have the same ID, so it won’t be able to receive the message from the website.

On Firefox, IDs are also unique (for example, I can’t upload a new extension on AMO, with a browser_specific_settings.gecko.id value that is an already existing ID on the AMO store.
So yeah, basically, I’m looking for an alternative to externally_connectable…

When I read about externally connectable on both Chrome and Firefox, it appears to be all from the perspective of the extension, in terms of from which other extensions and web pages the main extension will accept messages. Where is it stated that the web page can control which extensions it communicates with when posting a message that could be heard by any listening extension? Externally connectable appears to be a protection for the extension and not the web page.

Where does it read that:

Only the extension which has that ID can listen/receive the message.

It would appear from this Chrome link that it means that your extension will communicate only with your web page but not that another extension cannot communicate with your web page. It would appear that a copy of your extension could communicate with your web page by setting the URL match pattern and not be concerned about the id.

Would that not be handled by the content script of your extension communicating with the background script?

When a content script requests a communication port with the background script, a “name” property can be passed with the request, which could be used to pass a code that the background script can validate and refuse/close the port. That way the extension can refuse the web page, even if it’s in the manifest’s URL match pattern.

https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/connect

Likely, you know all of that already. I’m just not following why you want to post a message from a web page and control which extensions can hear it, rather than establishing communication between your web page and your extension through a content script. That appears to be what @ hans_squared linked to and that warns is not a secure method since any extension can listen for that message due to the fact that the source or target origin for that message cannot be specified. That is said to be a security measure, perhaps due to the fact that extensions in Firefox cannot currently choose to decline those messages via externally connectable; I don’t know.

However, why can you not achieve the same using a content script? Since the scenario appears to be that you also own the web page(s) that the extension will act upon, you don’t need to worry about “the guest page snooping.”

It doesn’t appear that externally connectable can prevent other extensions from acting upon your web page, but you don’t need externally connectable to control what extension your web page communicates with or what web pages your extension communicates with. You can limit it to your web page alone via the manifest match pattern.

Am I still missing the point of your question?

Also, if you have not seen this already, the bug discussion is here. I can’t understand it all but it appears to give some advice.

Hello,

As I explained, I want my website to be able to provide a set of features only to users who have installed our official extension.
Our extension is using a reward model. Users who installed it get additional features on our site. So of course, we don’t want someone to just make a clone of our extension to get the rewards without the official extension installed.

So, if someone was to clone our extension and publish it, our website needs to detect that it’s not the official extension, and wouldn’t provide the features.
This is the reason. This is a legitimate use case.

As this link explains, externally_connectable means that the extension can be connected to from outside.

The extension is not the one initiating the communication. It is a receiver, at least that’s my understanding here.
It is not the content script that initiates the communication, it is the website.

The website says "I want to communicate with extension ID {id}, and the extension with the correct ID is able to respond.

Are you saying that a clone of my extension would be able to intercept the message even if it has a different ID?

Again, I am looking for pointers here, but none of the answers I have read here provide an answer.

Thanks,
Francois

Just to be clear, the question is not wether or not externally_connectable is the right way to do. The question is:

  • How do I create a way for a website to know that a particular extension ID is installed, and that it is not an extension clone with a different ID?

If there’s no solution, so be it. But content scripts are not a solution to this question.

Perhaps I am still misunderstanding, for, although there isn’t currently a way in Firefox extensions to post a message with a target ID, I don’t see why the same level of security cannot be achieved as when using externally_connectable.

Why do you have to connect externally rather than following the Firefox extension communication methods?

In the extension manifest, the URL match pattern limits which web pages the extension will act upon, and that can be your web pages only. The extension injects a content script into your web page only which establishes a communication port with the background script of your extension. Thus, communication is limited between the two rather than posting a message from a web page directly to an extension that may be listening. The web page communicates with the content script which communicates with the background script, but, of course, the content script can listen for events also in the web page and there are ways to share objects between the web-page scripts and content scripts.

I’m far, far from an expert, but my understanding is that using browser.runtime.connect() can limit communication between the web page and the extension through the content script, such that there cannot be any undesired communication with other extensions because the message is not being posted for any listener to hear.

In that bug discussion link, one poster wrote that " 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. I don’t know if that is true or not but it is not the way it works in Firefox extensions, because the content script is the means of communication.

Instead of the extension listening for a message from any web page that passes the correct ID in a post message, the extension would first listen to which web pages are loaded and, when the specific URL match pattern is met, will inject the content script into that page to establish communication between the two. After that communication is established, either side can initiate communication.

In my limited understanding, the answer to

How do I create a way for a website to know that a particular extension ID is installed, and that it is not an extension clone with a different ID?

is that you cannot do exactly that, but you can create an extension that knows which web pages are loaded in the browser and choose with which ones it should communicate. That seems to be the same result.

If that doesn’t get to the fundamentals of your question, then as said by jscher2000, it’s over my head and I apologize for having wasted your time.

Also, where you ask

Are you saying that a clone of my extension would be able to intercept the message even if it has a different ID?

I’m not certain but I’m pretty sure that is what is being said in that bug report link as recent as four or six months ago.

Thanks for your reply Gary,
I think I understand the solution you describe. It is a good way to enable communication to a website, but it does not prevent someone to copy my extension and create their own extension based on it.

When I say “create a copy”, imagine that the user simply downloads my addon zip file. (they can be downloaded as xpi files, which are zip files, there’s no protection around that),

Now, this extension is a copy. It has the same functionalities as my extension.
So this copy will able to communicate with my website as well. The website has no way to know that the extension is not my official extension, but a copy of it.

you can create an extension that knows which web pages are loaded in the browser and choose with which ones it should communicate. That seems to be the same result.

That’s not the use case I want at all. I don’t want the extension to know which page is loaded. I want my web page to know that the extension is my official extension. That’s completely different, right?
From what I gather, there’s no solution for that, too bad.

And externally_connectable basically is not a good solution either, because an extension could modify the site’s javascript by injecting content scripts and changing the original script, so there’s still a flaw even with externally_connectable.

Thanks!
Francois

Thank you for the added explanation.

Where you write:

That’s not the use case I want at all. I don’t want the extension to know which page is loaded. I want my web page to know that the extension is my official extension. That’s completely different, right?

I think I understand why it appears to be different but I don’t think it is different. Using externally_connected, the web page posts a message to an extension ID and the extension will receive it if the URL of the page is in it’s manifest. If your extension ID is unique and a copy of the extension must have a different ID, then the posted message won’t be received by the copy.

That is what runtime.connect() does also in Firefox. It communicates with the specific extension only and an ID can be passed as the first parameter if communicating with a different extension other than your own.

The only difference I can see is that Chrome let’s you send the message directly from the page script and Firefox requires it come from the content script. Chrome limits which URLs an extension will communicate with and Firefox limits which URLs the extension will act upon. They are a little different but they provide the same protection, except perhaps that externally_connected may protect the extension a bit better without writing code to test the source of a message.

Neither protects your web page from being acted upon by another extension developed from a copy of your code, regardless of it’s ID; nor does either know if an extension ID is the official version or a stolen copy.

And you earlier asked:

How do I create a way for a website to know that a particular extension ID is installed, and that it is not an extension clone with a different ID?

This SO question gives some information on how to check if your extension has been installed in Firefox. The latter portion of the top answer involves methods found here on MDN.

I don’t know if these SO suggestions would satisfy your requirements of a copy. If the copy altered the web page in the same manner, then you might think your extension is loaded when it is not. But I don’t see how an published extension would be permitted to do this.

I’m pretty sure you can still use the extension ID within your scripts. These links explain a bit and where to set an ID in the manifest, if that would be useful. I’ve used the GUID for native messaging.

https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_specific_settings

https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/id

Would you please further explain the scenario you mentioned?

When I say “create a copy”, imagine that the user simply downloads my addon zip file. (they can be downloaded as xpi files, which are zip files, there’s no protection around that),

I didn’t know that the extension files could be downloaded apart from a direct install into the browser from AMO unless the developer chose to distribute the extension apart from AMO. I was under the impression that getting the extension signed and distributed on AMO eliminated the option to download the files, unless the developer wanted to distribute the extension through other means.

How are you planning to distribute the extension and would the requirement that extensions be signed not protect you?

You wrote that someone could clone the extension and publish it. How would they get so near an exact copy published? Would it not be caught in the review process before being signed? Or would it be submitted to be an unsigned extension and privately distributed? I don’t know that an unsigned extension can be installed any longer even under these conditions.

Have you experienced issues like this before with attempted copies of your extensions that were published?