This is a developing incident. StepSecurity is actively investigating this supply chain attack. We are continuously identifying additional compromised packages and will update this post as new information becomes available.
Summary
StepSecurity has identified an active, self-replicating supply chain attack spreading across the npm registry. The attack uses a technique we have not previously seen at this scale: instead of relying on package.json lifecycle hooks like preinstall or postinstall (which security tools routinely flag), the attacker injects a binding.gyp file that causes node-gyp to silently execute malicious code during npm install.
The malware is a full CI/CD worm. Once it runs in a developer's environment or CI pipeline, it harvests credentials from npm, GitHub, AWS, GCP, Azure, HashiCorp Vault, Kubernetes, and RubyGems. It then uses those credentials to publish poisoned versions of other packages the victim maintains, spreading itself further across the ecosystem. It also injects malicious steps into GitHub Actions workflows to ensure it runs on every future CI job in affected repositories.
So far, StepSecurity has identified compromised versions across several npm packages. The list continues to grow as the worm propagates.
What makes this attack different: the binding.gyp technique
Most npm supply chain attacks inject malicious code into the preinstall or postinstall scripts in package.json. Security tools, code reviewers, and npm's own audit systems focus heavily on these hooks.
This attacker bypasses all of that. Instead, they add a small binding.gyp file to the package. When npm sees a binding.gyp, it automatically invokes node-gyp to compile what it assumes is a native addon. The attacker exploits node-gyp's shell expansion feature in the sources array to execute arbitrary code:
{
"targets": [
{
"target_name": "Setup",
"type": "none",
"sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
}
]
}
This runs node index.js silently during install. The package's package.json scripts section contains only legitimate build commands. There is no postinstall hook to raise suspicion. The binding.gyp file is only 100 bytes. The malicious index.js it triggers, however, is 4.5 to 4.9 MB of obfuscated code.
How the attack works
The malware executes in three stages:
Stage 1: Obfuscated loader. The root-level index.js uses a ROT-N Caesar cipher to decode an inner script, which then decrypts two AES-128-GCM encrypted payloads using hardcoded keys.
Stage 2: Runtime download. The first decrypted payload silently downloads the Bun JavaScript runtime (v1.3.13) from GitHub into a temporary directory. This gives the attacker a fast, standalone runtime that avoids leaving obvious traces in the Node.js process tree.
Stage 3: CI/CD worm. The second decrypted payload (~720 KB) is the main worm. It runs via the downloaded Bun runtime and performs four operations:
- Credential harvesting. It scans the environment for npm tokens, GitHub tokens and PATs, AWS access keys (including IMDSv2 and ECS task role endpoints), GCP service account credentials, Azure client secrets and Key Vault contents, HashiCorp Vault tokens (checking multiple file paths and the local Vault API), Kubernetes service account tokens, RubyGems API keys, and passwords from 1Password CLI, gopass, and pass. It also extracts masked secrets from GitHub Actions runner process memory.
- GitHub Actions workflow injection. Using stolen GitHub tokens, the worm modifies CI/CD workflow files in repositories the victim can push to. It injects a
setup-bunstep and a payload execution step, ensuring the worm runs on every future CI job. - Package poisoning. Using stolen npm or RubyGems tokens, the worm queries the registry for all packages the victim maintains, downloads them, injects the malicious payload, and publishes new poisoned versions. This is how the worm spreads from one compromised account to dozens of packages.
- Exfiltration. Stolen credentials are encrypted with a hardcoded RSA public key and exfiltrated to attacker-controlled GitHub repositories as "dangling commits" (commits not reachable from any branch), making them difficult to discover through normal repository browsing.
Compromised packages
The following table lists all packages and versions identified as compromised so far. All were published between June 3 and June 4, 2026. This list is being updated as we identify additional affected packages.



