NEWS
Microsoft Flags 14 Malicious npm Packages Stealing Cloud Secrets
A single attacker pushed 14 malicious npm packages to the public registry in roughly four hours on May 28, 2026, according to Microsoft, and each one was built to grab cloud and build-pipeline credentials the second a developer ran npm install. Microsoft and the npm team have since taken the packages and the maintainer account offline.
The takedown is the easy part. By the time those packages vanished, the credential harvester inside them had already had a window to read AWS keys, HashiCorp Vault tokens, GitHub Actions secrets and, most consequentially, npm publish tokens off any machine that installed them. Stolen publish tokens are how a 14-package campaign turns into the next 140.
Fourteen Packages, One Maintainer, Four Hours
The maintainer account was brand new. According to Microsoft, whose Defender threat-intelligence team flagged the activity in a detailed breakdown of the npm typosquat campaign, an alias called vpmdhaj registered on the public npm (Node Package Manager) registry, tied only to a throwaway Gmail address, and started shipping within hours. By the time it stopped, 14 packages were live, each styled to pass for the cluster-management and configuration libraries that working developers pull every day.
The names did the social engineering. Entries such as opensearch-setup, opensearch-config-utility, elastic-opensearch-helper and env-config-manager lean on muscle memory and typos. Several went further, setting the homepage, repository and bug-tracker fields in their package.json to point at the genuine OpenSearch project on GitHub, so a quick glance at the listing showed a trusted upstream link.
Then there were the version numbers. Instead of the 0.1.0 you would expect from a day-old package, releases jumped straight to tags like 1.0.7265 and 2.1.9201, faking years of mature release history. The choice of OpenSearch and ElasticSearch lures was not random either; that crowd tends to keep AWS and Elastic cloud credentials close at hand.
- 14 malicious packages, scoped and unscoped, published under one identity.
- 4 hours from the first upload to the last of the cluster going live.
- 195 KB single-file credential harvester dropped as the second stage.
- 16+ AWS regions swept for stored secrets by that payload.

From a Noisy Beacon to a Borrowed Runtime
Every package in the cluster carried an automatic install-time hook, so the malicious code ran the moment someone executed npm install. No victim code had to import anything first. That part is old news in supply-chain attacks; npm’s own documentation on package lifecycle scripts spells out that preinstall and postinstall scripts run with full user permissions.
What stands out is how the loader changed mid-campaign. The earliest builds, version 1.0.7265 and below, used a chatty design. A preinstall.js script collected host details, base64-encoded them, and beaconed out to a command-and-control server (C2, the attacker’s remote control endpoint) with a campaign-specific X-Supply: 1 header.
That same server then handed back a compressed binary called payload.bin, which the stager wrote to disk, made executable, and spawned in the background. The package also re-launched that binary on every later require() of the module, a quiet way to survive across build stages and rebuild loops.
Later builds went silent. From version 1.0.7266 on, a single setup.mjs loader checks whether the legitimate Bun runtime is already installed and, if not, downloads a real, signed release of the open-source Bun JavaScript runtime straight from its GitHub release page, then uses it to run a payload already bundled inside the npm tarball. No custom binary fetch, no suspicious install-time callout. Defenders watching for odd outbound traffic during installs just see a developer tool downloading itself.
| Trait | Gen-1 (≤ 1.0.7265) | Gen-2 (≥ 1.0.7266) |
|---|---|---|
| Loader file | preinstall.js and index.js | setup.mjs |
| Install-time network call | HTTP C2 beacon with X-Supply: 1 | None; fetches a trusted runtime release |
| Second stage | payload.bin downloaded from C2 | Pre-bundled in the tarball, run via the runtime |
| Persistence | Re-spawns on every require() | Runtime-launched stage two |
| Detection footprint | Noisy, easy proxy signal | Low, blends with normal tooling |
What the Harvester Reaches For
The second stage is a single-file program, roughly 195 kilobytes, built for one job: scraping secrets out of cloud and build environments. Static analysis of the bundle, per Microsoft, shows routines aimed at five distinct targets.
- AWS: it queries the EC2 Instance Metadata Service (IMDSv2, the link-local endpoint at 169.254.169.254 that hands running instances their credentials) and ECS task metadata, calls STS (the AWS Security Token Service) GetCallerIdentity and AssumeRole, then enumerates Secrets Manager across more than a dozen regions with a bundled request signer.
- HashiCorp Vault: it reads the VAULT_TOKEN and VAULT_AUTH_TOKEN environment variables.
- npm: it validates stolen tokens through the registry’s whoami endpoint and lists exactly what those tokens are allowed to publish.
- GitHub Actions: it grabs repository and runner-OS context to spot continuous integration and delivery (CI/CD) build environments worth prioritising.
- Evasion: it resets the CI environment flag to false to slip past build-aware code paths, and refuses to re-run itself once a copy is already going.
The order of operations is the worrying part. Reading instance metadata and assuming roles gives an attacker live cloud sessions; enumerating Secrets Manager turns those sessions into a pile of downstream keys. AWS publishes guidance on hardening the Instance Metadata Service precisely to blunt this kind of theft, but it only helps on instances that actually enforce it.
Why Pulling the Packages Doesn’t Close the Door
Removing the 14 packages and the account behind them stops new victims. It does nothing for the secrets already taken.
Publish Tokens Are the Bigger Prize
Most of the stolen material is bad in a familiar way: cloud keys get abused, data gets pulled out, sessions get hijacked. The publish tokens are different in kind. A publish token lets the holder push new versions of whatever packages the victim maintains, so one compromised developer becomes a launchpad for the next batch of poisoned releases.
That is the mechanism that turns a contained incident into a moving target. The packages everyone is now blocking are the ones that already got caught; the npm publish tokens scraped off CI runners are the seed for whatever ships next under a name your build already trusts.
A Worm Lineage, Not a One-Off
This design reduces visibility for defenders that primarily monitor unusual outbound traffic during package installation.
That line, from Microsoft’s Defender Security Research team, describes the Gen-2 loader, and it doubles as a description of where these attacks are heading. The campaign sits in a lineage. Microsoft’s own signature for the payload, Trojan:JS/ShaiWorm, nods to Shai-Hulud, the self-replicating npm worm that tore through hundreds of packages in late 2025 and came back this spring as the so-called Mini Shai-Hulud waves.
Those earlier campaigns proved the math: stolen publish tokens plus an automatic preinstall hook equal self-propagation, and they drew a federal advisory on the npm supply-chain compromise from CISA. The vpmdhaj cluster reads like the same playbook, hand-run by a smaller operator who has not yet flipped the propagation switch.
How to Tell If Your Builds Touched It
If your team installed anything new from npm on or after May 28, the prudent assumption is that you need to check, not that you are clear. Microsoft’s guidance for responders is concrete.
- Find any system that installed or built one of the listed package versions on or after May 28, 2026, starting with lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml) and build logs.
- Run installs with –ignore-scripts, or set it globally, with equivalent flags for pnpm and yarn, so preinstall hooks cannot fire, and pin known-good versions instead of auto-upgrading.
- Rotate every credential that may have been exposed: AWS IAM and STS, HashiCorp Vault, npm publish, and GitHub Actions tokens that touched affected runners or developer machines.
- Block egress to the campaign’s C2 domain at DNS, firewall and proxy layers, and alert on any HTTP request carrying the X-Supply: 1 header.
- Hunt CloudTrail for a GetCallerIdentity call quickly followed by AssumeRole, and for Secrets Manager listing or reads jumping region to region from build infrastructure.
There is one small mercy. Microsoft reports that Defender Antivirus quarantined the Gen-2 loader, setup.mjs, the instant the tarball hit disk during its own analysis, and that the second stage now carries detections across endpoint, cloud and identity tooling. Teams that watch for a Node process pulling a developer runtime from GitHub Releases, or for detached child processes spawned with the campaign’s __DAEMONIZED marker, have clean signals to hunt on.
The packages are gone and the signatures are written. What happens next depends on speed: rotate the exposed tokens before the attacker uses them and this stays a 14-package footnote; leave them live on a CI runner with publish rights, and the same harvester ships again under a name your pipeline already trusts.
Frequently Asked Questions
Which npm packages were part of the vpmdhaj campaign?
All 14 were published on May 28, 2026 and have since been removed. They include scoped entries such as @vpmdhaj/elastic-helper, @vpmdhaj/devops-tools, @vpmdhaj/opensearch-setup and @vpmdhaj/search-setup, plus unscoped lookalikes including opensearch-setup, opensearch-setup-tool, opensearch-config-utility, opensearch-security-scanner, search-engine-setup, search-cluster-setup, elastic-opensearch-helper, env-config-manager and app-config-utility.
How do I check whether my build installed an affected package?
Search your lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml) and build logs for any of the package names, then look for a file named payload.bin inside a node_modules install directory. Prioritise machines that ran npm install on or after May 28, 2026, since the malicious code executes at install time rather than when your own code runs.
Does running npm install with –ignore-scripts fully protect me?
It blocks the install-time hook that this campaign relies on, which neutralises the initial execution, but it is not a complete defence on its own. Pair it with pinned, known-good versions, equivalent flags for pnpm and yarn, and a review of anything already installed, because a package that ran before you set the flag could have executed already.
What credentials should I rotate if I was exposed?
Rotate AWS IAM and STS sessions, HashiCorp Vault tokens, npm publish tokens, and GitHub Actions tokens that were present on any affected runner or developer workstation. The npm publish tokens matter most, because a stolen one lets an attacker push tainted versions of packages you maintain to other people.
What network indicator should defenders block first?
Block outbound traffic to the Gen-1 C2 domain aab.sportsontheweb[.]net at DNS, firewall and proxy layers, and raise an alert on any HTTP request that carries the X-Supply: 1 header, which is unique to this campaign. For the stealthier Gen-2 builds, watch for a Node.js process downloading the Bun runtime from GitHub Releases.
-
MICROSOFT 3651 week agoMicrosoft’s Copilot Super App Chases Its Own 450M Base
-
NEWS2 weeks agoMicrosoft Build 2026 Skips Windows 12 for the AI Bet That Counts
-
AZURE1 week agoAnthropic Hits $965B, and Microsoft Profits Either Way
-
NEWS2 weeks agoWindows 11 Low Latency Profile Lands in KB5089573 Update
-
AZURE3 days agoMicrosoft’s MAI Models Signal a Five-Year Bet on AI Independence
-
NEWS2 weeks agoCall of Duty Warzone Delisted on Xbox One and PS4 June 4
-
NEWS2 days agoXbox Games Showcase 2026: Start Time, Expected Games, What to Watch
-
NEWS1 week agoModern Warfare 4 Skips Day One Game Pass, Lands Oct 23
