By clicking “Accept”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.
18px_cookie
e-remove
Blog
Glossary
Customer Story
Video
eBook / Report
Solution Brief

The Return of PhantomRaven: Detecting Three New Waves of npm Supply Chain Attacks

Endor Labs security researchers identified 88 malicious open source packages belonging to three new waves of the PhantomRaven campaign.

Endor Labs security researchers identified 88 malicious open source packages belonging to three new waves of the PhantomRaven campaign.

Endor Labs security researchers identified 88 malicious open source packages belonging to three new waves of the PhantomRaven campaign.

Written by
Rachana Misal
Rachana Misal
Kiran Raj
Kiran Raj
Published on
March 10, 2026
Updated on
March 10, 2026

Endor Labs security researchers identified 88 malicious open source packages belonging to three new waves of the PhantomRaven campaign.

Endor Labs security researchers identified 88 malicious open source packages belonging to three new waves of the PhantomRaven campaign.

Endor Labs identified 88 new malicious npm packages belonging to three new waves (Wave 2, 3, and 4) of the PhantomRaven campaign distributed between November 2025 and February 2026., At the time of writing, the campaign remains active: 81 of the 88 packages are still available on npm, and two of the three new command-and-control servers continue to operate.

PhantomRaven is a software supply chain attack that uses Remote Dynamic Dependencies (RDD) to hide credential-stealing malware in non-registry dependencies that bypass standard security scanning. The first wave affecting 126+ packages with over 86,000 downloads, was first described by Koi Security in October 2025. 

By correlating infrastructure indicators, code similarities, and attacker operational patterns, we were able to track the evolution of the campaign and identify new waves as they emerged. Throughout the campaign, the attacker rotated infrastructure, modified operational details (such as PHP endpoint names, dependency names, and npm package descriptions), and used more than 50 disposable npm accounts. Despite these changes, the underlying malware payload remained largely unchanged. 

Infected packages

Wave-2

Wave-3

Wave-4

Indicators of Compromise (IOCs)

All four C2 domains registered through Amazon Registrar, Inc. with AWS Route53nameservers, Identity Protection Service WHOIS privacy, and zero TLS certificates.

Timeline

We classifed each wave by its C2 domain, the attacker-controlled server that hosts the malicious payload. Each wave uses a distinct domain and IP address, while other attributes like the dependency name in package.json and the PHP endpoint for data exfiltration evolve across and sometimes within waves. The underlying malware payload remains functionally identical throughout.

Date Event
August 2025 PhantomRaven campaign begins. First 21 packages published to npm.
August 2025 npm detects and removes the initial batch.
Sep–Oct 2025 Attacker publishes 80+ additional packages, evading detection.
October 29, 2025 Koi Security publishes the first PhantomRaven disclosure: 126 packages, 86,000+ downloads.
October 31, 2025 Sonatype identifies 83 additional wave-1 packages, bringing the total to 200+.
November 4, 2025 Attacker registers new C2 domain: jpartifacts.com via Amazon Registrar.
November 13, 2025 Wave-2 begins. First package (transform-react-jsx) published.
Nov 2025 – Feb 10, 2026 Wave-2 ramps up: 50 packages published across 26+ accounts.
February 13, 2026 Attacker registers storeartifacts.com — Wave-3 C2 domain. First 2 Wave-3 packages published the same day.
Feb 13–17, 2026 Wave-3: 34 packages published across 25+ accounts. RDD dependency name evolves from ui-styles-pkg to js-pkg.
February 18, 2026 Attacker registers artifactsnpm.com — Wave-4 C2 domain. All 4 Wave-4 packages published the same day. PHP endpoint renamed from jpd.php to npm.php.
March 2026 Endor Labs identifies all three waves through infrastructure correlation. 2 of 3 C2 servers are still live. 81 of 88 packages remain on npm.

Understanding the PhantomRaven Campaign

PhantomRaven was a large-scale npm supply chain attack affecting 126+ packages with over 86,000 downloads, first described by Koi Security in October 2025.

The download of second stage payloads by attacker-owned code is a very common pattern found in many malicious packages, intended to reduce the footprint of malicious code in a compromised package itself. 

PhantomRaven introduces an interesting variation by delegating the download to npm's resolution mechanism. By specifying an HTTP URL instead of a version range in dependencies of package.json, npm itself fetches the malicious payload from the attacker's server during a normal install, no explicit attacker code required. Koi Security termed this technique Remote Dynamic Dependencies (RDD). The packages themselves contained nothing but a benign "Hello World" script, while npm silently retrieved and executed the real payload as part of its standard dependency resolution.

Identifying PhantomRaven

Endor Labs was able to detect the malicious activity by analysing package code, infrastructure signals and dependencies fetched from non-npm registries. Below are the screenshots of Endor’s detection and clustering pipeline:

Detection reasons for transform-jscript@9.9.0 NPM package
Screenshot of EndorLabs Campaign detection dashboard. We were able to correlate 72 IoCs across 80+ packages for PhantonRaven Campaign

The Infection Chain

  1. Developer runs npm install 
  2. npm reads package.json → finds a URL dependency pointing to the attacker's C2: "http://packages.storeartifact.com/npm/"
  3. npm fetches the .tgz from the attacker's server (NOT from npmjs.com)
  4. Downloaded package has a preinstall hook node index.js
  5. Malicious index.js executes automatically: 
    1. Harvests emails from .gitconfig, .npmrc, env vars
    2. Collects CI/CD tokens (GitHub, GitLab, Jenkins, CircleCI)
    3. Fingerprints the system (IP, hostname, OS, Node version)
    4. Exfiltrates everything to packages[.]storeartifact[.]com/jpd.php
    5. Data sent via HTTP GET → HTTP POST → WebSocket (redundant exfil)

Wave-1 Scale

  • 200+ malicious packages (126 initially reported, 83 more found by Sonatype) 
  • 86,000+ downloads 
  • 14+ attacker-controlled npm accounts using sequential emails: jpdtester01@hotmail.com through jpdtester13@gmail.com 
  • C2: packages.storeartifact.com / 54.173.15.59:8080 (AWS EC2, us-east-1)

Linking the waves using infrastructure and operational fingerprints

All four waves share a remarkably consistent infrastructure pattern. The correlation was immediate and unmistakable: same actor, rotating infrastructure, same payloads.\

Attribute Wave-1 Wave-2 Wave-3 Wave-4
C2 Domain packages.storeartifact.com npm.jpartifacts.com package.storeartifacts.com npm.artifactsnpm.com
Domain Created 2025-08-07 2025-11-04 2026-02-13 2026-02-18
C2 IP 54.173.15.59 100.26.42.247 13.219.250.107 54.227.45.171
C2 Port 8080 / 80 80 80 80
C2 Status DOWN LIVE DOWN LIVE
Hosting AWS EC2 AWS EC2 AWS EC2 AWS EC2
Registrar Amazon Registrar, Inc. Amazon Registrar, Inc. Amazon Registrar, Inc. Amazon Registrar, Inc.
WHOIS Privacy Identity Protection Service Identity Protection Service Identity Protection Service Identity Protection Service
DNS AWS Route53 AWS Route53 AWS Route53 AWS Route53
TLS Certificates 0 (HTTP only) 0 (HTTP only) 0 (HTTP only) 0 (HTTP only)
Exfil Endpoint http://packages.storeartifact.com/jpd.php http://npm.jpartifacts.com/jpd.php http://package.storeartifacts.com/npm.php http://npm.artifactsnpm.com/npm.php
RDD dependency name ui-styles-pkg (per public reports) ui-styles-pkg ui-styles-pkg / js-pkg ts-pkg / js-pkg

All four domains contain the word "artifact.” a deliberate choice to appear as a legitimate package artifact server. The naming convention (storeartifact → jpartifacts → storeartifacts → artifactsnpm) shows the attacker maintaining thematic consistency while rotating domains to avoid blocklists. 

Every domain was registered through Amazon Registrar with the same Identity Protection Service proxy, AWS Route53 nameservers, and zero TLS certificates (all C2 communication is plaintext HTTP). The identical WHOIS fingerprint across four domains registered over seven months provides strong evidence of a single operator using the same AWS account.

The Wave-2 C2 domain was registered on November 4, 2025, just 6 days after Wave-1 was publicly disclosed on October 29. Wave-3 infrastructure was registered on February 13, 2026, three days after the last Wave-2 package. Wave-4 appeared just one day after the last Wave-3 package as the attacker was racing to stay ahead of takedowns.

The attacker changed npm-facing metadata to “MAX” / “ZOD” in Wave-4, but never updated the C2 tarball, which still contains "author": "JPD" across all waves,- including Wave-3 (confirmed via recovered payload) and Wave-4. This alone confirms the same operator is behind all waves.

What changed across waves

While the core technique and payload remain identical, each wave introduces notable changes in operational security.

Changes in attacker OpSec

1. Email pattern evolution

Wave-1 used a mix of email providers:

jpdtester01@hotmail.comjpdtester13@gmail.com (hotmail, outlook, gmail — scattered)

Wave-2 onwards standardized on Outlook, but with evolving prefixes. All contain "jpd" or "dharsh" in some variation:

2. Account naming evolution

The pluginjpd account series bridges Wave-3 and Wave-4: numbers 01–11 in Wave-3, 12–15 in Wave-4.

Wave Example
Wave-1 npmhell, packagejpd, micropackage1
Wave-2 (early) jpdnpmjpd01 through jpdnpmjpd20
Wave-2 (expansion) dharsh00, dharshjpd00, jpdjpd03
Wave-3 npmjj03, dharshanjp01, jpddharsh02, pluginjpd0111
Wave-4 pluginjpd1215

3. npm package metadata evolution

Every npm package has metadata fields — description and author — that are visible on the npmjs.com package page. These are set by the attacker when publishing the package. Separately, the C2 tarball (the malicious payload fetched from the attacker's server) contains its own package.json with an author field.

Field Where it appears Wave-1 Wave-2 Wave-3 Wave-4
description npm package page Various Dharshan JPD MAX / ZOD
author npm package page Various JPD JPD MAX / ZOD
author C2 tarball package.json JPD JPD JPD JPD

The npm-facing metadata changed in every wave — the attacker rotated the description and author strings visible on npmjs.com, switching to "MAX" / "ZOD" in Wave-4. But the author field inside the C2 tarball was never updated and still reads "JPD" across all waves, including Wave-3 (confirmed via recovered payload) and Wave-4.

4. Publication cadence

Wave-2 started slow and accelerated over 3 months: 

  • Nov 13 2025: First package (transform-react-jsx) 
  • Dec 15 2025 – Jan 30 2026: Sporadic batches (dharsh/dharshjpd accounts)
  • Feb 5 - 10 2026: Rapid-fire jpdnpmjpd series - 20 packages in 5 days

Wave-3 was even faster: 34 packages in 5 days (Feb 13–17), escalating from 2/day to 12/day.

Wave-4 published all 4 packages in a single day (Feb 18), the day after the last Wave-3 package.

Code changes: what's different in the payload

We obtained Wave-1 RDD payloads (@dtpk-cc/components, airbnb-flow, unused-imports) and performed byte-level diffs against the live Wave-2 RDD payload (from http://npm.jpartifacts.com/npm/jam3), a recovered Wave-3 RDD payload (`google-camelcase`, obtained from an archived tarball) and the live Wave-4 RDD payload (from http://npm.artifactsnpm.com/npm/sort-export-all). 

The payload code is identical across all waves except for two lines - the C2 URL.

Wave-1 (lines 162–163):

Wave-2 (same lines - only the domain changed):

Wave-3 (domain changed AND PHP endpoint renamed for the first time)

Wave-4 (new domain, same renamed PHP endpoint):

C2 endpoint variants

Wave C2 Endpoint Packages
Wave-1 (early) http://54.173.15.59:8080/jpd.php airbnb-flow and others
Wave-1 (later) http://packages.storeartifact.com/jpd.php unused-imports, @dtpk-cc/components, most others
Wave-2 http://npm.jpartifacts.com/jpd.php All 50 Wave-2 packages
Wave-3 http://package.storeartifacts.com/npm.php All 34 Wave-3 packages (C2 down, confirmed via recovered tarball)
Wave-4 http://npm.artifactsnpm.com/npm.php All 4 Wave-4 packages

Change in the npm-published package 

While the RDD payload code is identical, the npm-published package did change. Wave-2 lists the RDD in both dependencies and devDependencies:

This ensures the malicious dependency is fetched regardless of how the package is installed — whether as a production dependency or a development dependency (e.g., npm install --production vs npm install).

What stayed the same

With primary-source samples from all four waves, we can confirm: 257 of 259 lines in the payload are byte-for-byte identical. The only difference is the C2 URL on lines 162–163. Everything else remains unchanged: 

  • The four email harvesting sources (`.gitconfig`, `.npmrc`, environment variables, and `package.json`)
  • The 20 CI/CD variables (GitHub Actions, GitLab CI, Jenkins, and CircleCI)
  • The system fingerprinting fields
  • The triple-redundant exfiltration chain (GET + POST + WebSocket)

Additional constants include:

  • The `wss://yourserver.com/socket` WebSocket placeholder
  • he `api64.ipify.org` public IP lookup service
  • The spoofed Windows Chrome User-Agent
  • The outer `Hello, world!` decoy
  • The `"JPD"` author in every C2 tarball
  • The dependency trio (`axios ^1.7.9`, `node-fetch ^3.3.2`, `ws ^8.18.0`).

Technical analysis

The trojan package

Every package published to npm is identical in structure: a harmless "Hello World" script and a package.json with a hidden URL dependency:

Image of package.json hosted on NPM

When a developer runs npm install jam3, npm sees the HTTP URL dependency and fetches a tarball from the attacker's server. The package on npm has zero malicious code; it's entirely in the RDD.

The RDD payload

The C2 returns a tarball containing two files. The package.json triggers automatic execution via the preinstall hook and the index.js (259 lines) is the full malicious payload.

Image of package.json hosted on C2 server

1. Developer Email Harvesting

Reads developer emails from four sources - environment variables, ~/.gitconfig, ~/.npmrc, and the local package.json author field:

2. CI/CD Pipeline Reconnaissance

Harvests environment variables from four major CI/CD platforms. This tells the attacker exactly which build pipelines the victim uses:

3. System Fingerprinting + Public IP Lookup

Builds a complete profile of the victim machine, including a call to api64.ipify.org to learn the public IP:

4. Exfiltration, Stealth, and Victim Lookup

All harvested data is sent twice, once as URL parameters (GET), once as a JSON body (POST) with a spoofed Windows Chrome User-Agent. If both fail, a WebSocket fallback is attempted. This triple-redundant approach maximizes exfiltration success across diverse network environments - GET bypasses POST-only inspection, POST handles URL length limits, and WebSocket evades HTTP-layer firewalls. 

The WebSocket URL (wss://yourserver.com/socket) is a placeholder - not yet wired to a real server, suggesting the attacker plans to deploy a WebSocket-based C2 in the future.

The code below also reveals the payload's stealth mechanism. The fetch() calls execute unconditionally - data is always exfiltrated. But all console.log and console.error calls are wrapped in if (!isPreinstall), meaning during npm install the victim sees zero terminal output while their data is silently sent to the C2.

Victim database lookup: After exfiltration, the payload calls lookupByEmail(primaryEmail), which queries the C2's lookup endpoint with the victim's harvested email. The endpoints object defines two keys — log for data collection and lookup for querying — and at runtime the lookup URL resolves to something like jpd.php?action=lookup&email=victim@example.com. This reveals the C2 is more than a one-way log collector — it maintains a queryable database that can cross-reference victims across packages. Since this lookup only runs outside of preinstall context, it likely serves as the attacker's own tool to verify data was stored successfully.

C2 server analysis

The live C2 servers (Wave-2 and Wave-4) both run Apache/2.4.58 on Ubuntu with only ports 22 (SSH) and 80 (HTTP) open - no HTTPS. The PHP endpoints return structured JSON confirming successful data logging:

Wave-2 C2 response to exfiltration (`jpd.php`):

This confirms the C2 backend is a PHP application with a structured API that stores and queries victim data.

Wave-4 C2 response to exfiltration (`npm.php`):

Both endpoints return the same structured JSON API — confirming the C2 backend is a PHP application that stores and queries victim data. The Wave-4 response shows all 14 data fields being logged successfully.

Captured Wave-4 exfiltration data (from pcap):

The exfiltration format is identical across all three waves: same 12 data fields, same dual GET+POST method, same spoofed User-Agent, same WebSocket fallback. Only the C2 domain and PHP endpoint name differ.

Slopsquatting

All three waves continue the slopsquatting strategy first seen in Wave-1, targeting package names that legitimate projects would plausibly use but that are not actually claimed.

Wave-2 targeting categories

Category Examples Impersonates
Babel plugins syntax-decorators, syntax-exponentiation-operator, transform-dev, minify-mangle-names @babel/plugin-* family
GraphQL Codegen typescript-resolvers, typescript-react-query, typescript-rtk-query @graphql-codegen/* namespace
ESLint plugins es6-recommended, prefer-let eslint presets
Real people / orgs developit, jam3 Jason Miller (Preact), Jam3 (agency)
Build tools yoshi-base, vitest-globals Wix's yoshi, Vitest
Scoped packages @storylane/uikit, @storylane/shared-packages Storylane's private packages

Wave-3 shifted heavily to Babel plugins

Examples Impersonates
transform-typescript, transform-spread, transform-for-of, transform-new-target, transform-dynamic-import @babel/plugin-transform-*
syntax-async-generators, syntax-function-bind, syntax-do-expressions, syntax-export-extensions @babel/plugin-syntax-*
add-react-displayname babel-plugin-add-react-displayname

Wave-4 continued the pattern

Examples Impersonates
sort-export-all, filter-imports, mui-path-imports ESLint/Babel import plugins
import-zod Zod validation library integrations

A notable evolution: Wave-2 had a heavy focus on GraphQL Codegen names (betting developers type the package name without the @graphql-codegen/ scope prefix), while Wave-3 pivoted almost entirely to Babel plugin names. Wave-4 targeted import/export utility names. The attacker is systematically working through different popular package namespaces.

Conclusion

PhantomRaven is not a sophisticated zero-day. Across multiple waves, the attacker reused the same technique, payload structure, and infrastructure patterns while only rotating superficial details such as domains, endpoints, dependency names, and npm accounts. The malicious functionality was delivered using Remote Dynamic Dependencies (RDD), where the published packages appeared benign while the real payload was fetched from external attacker-controlled servers during installation.

Because the malicious code never existed directly in the package, traditional scanners looking for obvious indicators such as obfuscation, suspicious `eval()` usage, or known bad URLs could easily miss it. Detecting campaigns like PhantomRaven requires looking beyond individual packages and following the external dependency chain and infrastructure patterns.

By combining package analysis with threat intelligence, we can correlate attacker behavior across waves and identify recurring patterns even when surface indicators change. This approach improves detection accuracy and enables more proactive protection for customers against evolving open source supply chain threats.

Malicious Package Detection

Detect and block malware

Find out More

The Challenge

The Solution

The Impact

Welcome to the resistance
Oops! Something went wrong while submitting the form.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.