Build1 distinct publisher3 min readPublished
Web-accessible extension assets sit at fixed URLs built from public IDs, so onload versus onerror answers installed-or-not without asking anyone. The only party who can close that door is the extension's author.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
Take the send-message path first, because it is the only one of the three that has a permission check at all. A page calls `chrome.runtime.sendMessage` with an explicit extension ID and a payload, and a reply means the extension is there and answered [2]. The browser will only deliver that message if the extension's manifest lists the calling page's origin under `externally_connectable` [2]. So this probe reaches exactly the extensions whose authors opted in, and no others [4]. The opt-in usually exists for a defensible reason. The author of the post maintains an extension that accepts page messages so a customer's bug-report button can tell whether the extension is installed [7]. The defect is in the match list, not the feature: `"matches": ["<all_urls>"]` does not mean the author's own customers, it means every origin on the internet may open a channel and ping [3]. That is one line of manifest, which is roughly the amount of review it tends to get.
The asset probe needs no opt-in. Web-accessible extension files resolve at a URL built from the extension ID, such as `chrome-extension://<extension-id>/icon-128.png`, and a page can point an `Image` at it and read `onload` as installed, `onerror` as absent [5]. Nothing in that sequence involves the extension author's consent beyond having published a web-accessible file, which the author says almost all extensions do [5][8]. Three inputs make it work: the ID, which is public and stable and readable off the store listing [6]; the path, which is whatever the author published; and the load event, which belongs to the browser. Only one of those three is under the author's control [15]. You cannot unpublish your ID and you cannot ask the browser to stop firing `onerror`. You can decide which files are reachable.
That asymmetry is the part worth carrying into a design review. Declining to accept page messages does not make an extension quieter, because the asset probe covers almost every extension while the messaging probe covers only the subset that listed an origin [16].
The bait techniques trade precision for coverage. A div classed `ad-banner ad-slot sponsored`, pushed offscreen at ten pixels high, then checked for `offsetHeight === 0` or a computed `display: none`, tells the page that something on this machine hides ad-like elements [10]. A hidden password input, watched for injected DOM around it, tells the page a password manager is present [11]. Neither says which one, and the author's position is that the category is often the fact that mattered [12].
The load-bearing claim here is a durability claim, and it is single-sourced: the author states that the asset technique is old, was demonstrated at scale by security researchers years ago, and still works today [9]. Treat that as a report about the browsers and extensions the author tested, not about yours. For it to transfer to your users, all three inputs have to hold at once for the extensions you care about: the `chrome-extension` URL scheme resolving in their browser, the ID being derivable from a store listing, and at least one web-accessible file in the package [17]. Two of those you can check for your own extension in about a minute.
In my context I now read `externally_connectable` as a published API surface and the web-accessible resource list as an index someone else can enumerate. If an extension needs a presence check to support a bug-report flow, the origin list can be the specific hostnames that flow actually runs on rather than the wildcard [3]. The cost of that choice is real and small: someone has to remember to update the manifest when the support site moves domains.
Ranked by verification strength, evidence, and original report placement.
The post argues that a web page's ability to detect installed extensions is not a Chrome bug but the sum of several features working exactly as designed, and that extension authors are the people best placed to close it while mostly not knowing they left it open.
A page can call chrome.runtime.sendMessage(EXTENSION_ID, { type: 'ping' }, callback) and treat a reply as proof the extension is installed and answered; for the message to be allowed, the extension must list the page's origin in its manifest under externally_connectable.
Authors who want their extension to work with any site use "externally_connectable": { "matches": ["<all_urls>"] }, which does not mean the author's customers' sites but means every site on the internet may open a channel to the extension and ping it.
The sendMessage technique is narrow because it only finds extensions that chose to talk to pages.
Any extension asset marked web-accessible is reachable at a fixed URL built from the extension's id, for example chrome-extension://<extension-id>/icon-128.png, so a page can set an Image src to that URL and resolve onload as installed and onerror as not installed, with no message, no permission and no cooperation from the author beyond publishing a web-accessible file.
Extension ids are public and stable and can be read off the store, so a page can carry a list of ids and the resource path each exposes, loop over it, and come out with an inventory.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · September 1, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
In Manifest V3 every message handler is a cold start, and the fix for lost state hides new failures1 distinct publisher
build
Firefox makes you name your extension's ID before AMO will take the package1 distinct publisher
build
The exit ping that never left: fetch in beforeunload loses to the browser's unload policy1 distinct publisher
build
The stroke width that never rendered: SVG attributes lose every cascade fight1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Checkable code, single witness
The strongest thing going for this story is that a reader can verify most of it in a console: the sendMessage ping, the chrome-extension:// image probe and the bait div stand or fall on documented Chrome behaviour, not on the author's authority. What rests on his word alone is scale — 'almost all' extensions ship a web-accessible file, researchers did this at scale 'years ago' — and neither arrives with a number, a sample or a citation.
Nobody counted anything
Detectability and detection are different questions and only the first is answered. We learn that fixed-URL probes still work and that Manifest V3 offers a cure, but not how many store extensions ship a stable web-accessible path today, how many have adopted use_dynamic_url, or how many sites are looping over ID lists right now. There is no measurement here to score.
Slightly ahead of the arithmetic
Unusually restrained for a browser-privacy post: the author calls his main trick old, hands credit to earlier researchers, and puts the fix on people like himself rather than on Google. The overreach is confined to two unquantified steps — 'almost all extensions', and the jump from a probe list to a near-unique identifier the user can never clear. Mechanically sound; the fingerprinting arithmetic is simply never shown.
Disclosed vendor stake
The author says up front that he maintains an extension plus a library that talks to it, and that his extension takes messages from pages so a customer's bug-report button can see it. That disclosure cuts both ways. It is why the manifest advice reads like it came from someone who has shipped and regretted a config, and it is also why 'do not use externally_connectable at all' lands as a competitor's recommendation to everyone who does. Published under a session-replay banner on dev.to, this is developer marketing as well as advocacy — openly so.
Firm mechanics, soft prevalence
We would hand a reader the snippets without hesitation and the store-wide claims with a caveat pinned to them. One publisher, one practitioner, no second account and no response from Google means anything that depends on how widespread the exposure is, or on whether the platform intends to change it, is outside what this reporting can settle.