57 Packages Compromised Without a Single Lifecycle Script
The Phantom Gyp technique ships a weaponized binding.gyp that triggers code execution
during npm install. No preinstall hook, no postinstall hook. It bypasses every
lifecycle script monitor on the market.
Two days after the Red Hat Miasma attack compromised 32 packages using provenance-valid lifecycle scripts, the same malware family pivoted. This time it didn't use lifecycle scripts at all.
On June 3 at 23:30 UTC, a compromised maintainer account published malicious versions of
@vapi-ai/server-sdk — the official Vapi voice AI SDK, 408,000 monthly downloads.
Over the next hour, 56 more packages fell across the autotel, awaitly, executable-stories,
and node-env-resolver families. 286 malicious versions total.
StepSecurity named the technique "Phantom Gyp." Snyk tracks the full incident.
The technique
Every major security tool monitors lifecycle scripts: preinstall,
postinstall, install. Socket flags them. Snyk scans them.
npm warns about them. The Red Hat Miasma wave two days earlier used preinstall
and got caught within hours.
Phantom Gyp doesn't use any of them.
Instead, it ships a 157-byte binding.gyp file. When npm install
detects a binding.gyp, it automatically invokes node-gyp rebuild
to compile native addons. The attacker's binding.gyp calls
node -e "<payload>" as a "build action" — executing arbitrary
JavaScript during install without triggering a single lifecycle script alert.
This is a 14-year-old npm behavior. It's documented. It's how native modules build. And until last week, nobody used it as an attack vector.
The payloads
Same Miasma/Shai-Hulud malware family. The payload harvested npm, GitHub, AWS, GCP, Azure, HashiCorp Vault, and Kubernetes credentials. It exfiltrated through attacker-controlled GitHub repos. It injected GitHub Actions workflows for persistence. And it self-propagated by republishing packages from every maintainer account it could reach.
The largest victim, @vapi-ai/server-sdk, is the primary way developers
integrate Vapi's voice AI platform. A compromised version in a CI pipeline could propagate
through every downstream project that depends on it.
What behavioral signals show
I scored the compromised packages with Commit:
| Package | Score | Publishers | Weekly DL | Age | Provenance |
|---|---|---|---|---|---|
| @vapi-ai/server-sdk | 47 | 5 | 100K | 1.1y | ✓ |
| ai-sdk-ollama | 49 | 1 | 42K | 0.8y | ✓ |
| autotel | 40 | 1 | 6K | 1.2y | — |
| awaitly | 32 | 1 | 287 | 0.5y | — |
| node-env-resolver | 28 | 1 | 92 | 0.4y | — |
Every package except @vapi-ai/server-sdk has a single npm publisher.
Three are under a year old. The attack surface is the same one Commit flags by design:
single-publisher packages with limited oversight.
The Red Hat packages two days earlier scored 65–83 — high enough that static scoring alone wouldn't have flagged them. These score 28–49. Behavioral signals would have.
What this changes
Lifecycle script monitoring is necessary but no longer sufficient.
binding.gyp opens a second execution path that most security tools don't scan.
And it's been there since 2012.
The Miasma lineage has now used three distinct execution vectors in three weeks:
- TanStack (May 11):
postinstalllifecycle script - Red Hat (June 1):
preinstalllifecycle script with valid SLSA provenance - Phantom Gyp (June 3):
binding.gypnative addon hook — no lifecycle script at all
Each iteration bypasses the defense that caught the last one. The constant across all three: compromised single-point-of-failure accounts.
Check your project
npx proof-of-commitment
Scores every dependency in your lockfile. Packages compromised in the Phantom Gyp attack
are flagged in results. If you depend on @vapi-ai/server-sdk,
ai-sdk-ollama, or any autotel/awaitly/executable-stories package —
verify you're on a clean version.