Back to News & Insights
JavaScript September 1, 2026 · 5 min read

A Web Page Can Tell Which Extensions You Have Installed. Here Is How.

Open a page and it can start guessing which browser extensions you run before you click a thing. Not "extensions in general" - *which ones*. Your password manag

A Web Page Can Tell Which Extensions You Have Installed. Here Is How.

Open a page and it can start guessing which browser extensions you run before you click a thing. Not "extensions in general" - which ones. Your password manager, your ad blocker, the wallet, the internal tool your employer ships, the accessibility extension you depend on. The page never asks and you never see it happen.

This is not a bug in Chrome. It is the sum of a few features working exactly as designed, and the people best placed to close it are extension authors who mostly do not know they left it open. I maintain an extension and a library that talks to it, so I have spent real time on the detectable side of this. Here is how a page does it, what the answer is worth to whoever is asking, and what actually stops it.

Some extensions accept messages from web pages on purpose - our own does, so a customer's "report a bug" button can tell whether the extension is there. The API is chrome.runtime.sendMessage:

For a page to be allowed to send that message, the extension has to list the page's origin in its manifest, under externallyconnectable. Authors who want their extension to work with any site reach for the wildcard:

And that one line is the door. does not mean "my customers' sites". It means every site on the internet may now open a channel to this extension - which means every site may ping it and learn whether you have it. The convenience the author wanted for their own pages, they handed to everybody's.

This technique is narrow, because it only finds extensions that chose to talk to pages. The next one is not narrow.

Extensions ship assets - icons, injected stylesheets, images. Any asset marked web-accessible is reachable at a fixed URL built from the extension's id:

Fixed. Which means a page does not have to be given permission to look. It just tries to load the file and watches what happens:

onload fires, the extension is installed. onerror, it is not. No message, no cooperation, nothing the extension author has to have agreed to beyond publishing a web-accessible file - which almost all of them do. Extension ids are public and stable; you read them off the store. So a page carries a list of ids and the resource path each one exposes, loops over it, and comes out the other side with an inventory. This is the workhorse, and it is old - security researchers demonstrated it at scale years ago and it still works today.

The two above need the extension's id. This one does not, and it catches the extensions that never talk to pages and ship no web-accessible files.

An extension that changes a page leaves marks, and a page can bait them out. An ad blocker hides elements that look like ads, so plant one and see if it vanishes:

A password manager injects an icon into password fields, so put a hidden password input on the page and watch for the DOM to change around it. You do not learn which ad blocker or which password manager, but you learn the visitor runs one - and often that is the fact that mattered.

It is a near-unique fingerprint. The particular set of extensions you run is close to an identifier, and unlike a cookie you cannot clear it. It rides along with every other fingerprint signal and sharpens all of them.

It deanonymises you by inference. This is the sharp end. Extensions are not neutral - many of them say something about the person: an internal SSO or admin extension that only one company ships → the visitor works there a specific screen reader or accessibility extension → an accessibility need, which is about as sensitive as attributes get a particular crypto wallet → the visitor holds crypto, which is exactly what a phishing page wants to know before it decides whether you are worth the effort a competitor's extension → this landing page can quietly greet their customer differently from everybody else

It happens before you do anything. Every technique above runs on page load. By the time you have read the headline, the page has tailored itself - or decided what you are - off a signal you did not know you were sending.

None of this requires a breach or a trick. It is the platform behaving as documented, aimed at a question you were never asked whether you wanted answered.

The good news is that the fixes exist and most of them belong to the extension author, not to you.

Two things there. usedynamicurl makes the resource's URL a per-session random token instead of the fixed path, so the probe has nothing stable to request. And matches narrows who may load the resource at all, instead of leaving it open to every origin. Ship a web- accessible file with neither and you are the reason technique two still works.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation