Armando found that @ctrl/ts-base32 v4.0.1, published September 15, 2025 at 19:52 UTC, was backdoored as part of the coordinated Shai-Hulud worm wave targeting the @ctrl npm namespace. With 516,699 total downloads, this Base32 encoder/decoder library had a 3.5 MB worm payload (bundle.js) and a postinstall lifecycle hook injected into the package, silently stealing cloud credentials from any environment that ran npm install.
Key Findings
- Malicious version: v4.0.1, published 2025-09-15T19:52:45Z; v4.0.2 is the worm's self-propagated follow-on
- Entry point: postinstall lifecycle hook fires automatically on npm install
- Targets: developer machines and CI/CD pipelines; harvests AWS, GCP, and ECS metadata credentials
- Exfiltration: stolen credentials sent to https://webhook.site/bb8ca5f6-4175-45d2-b042-fc9ebb8170b7
- Detection: Script-JS.Worm.SupplyChain, Text.Worm.SupplyChain
Package Background
@ctrl/ts-base32 is a Base32 encoder/decoder with support for multiple variants, serving 7 direct downstream dependencies with 516,699 cumulative downloads. The package was part of the broader @ctrl namespace and had a clean release history until the September 15, 2025 attack.
Attack Analysis
The attacker injected two malicious additions into the package: a bundled worm payload (bundle.js) and a postinstall hook that fires automatically when npm install completes. The hook invokes bundle.js, which queries the AWS Instance Metadata Service (IMDS) at 169.254.169.254, the GCP Metadata server at metadata.google.internal, and the AWS ECS Task Metadata endpoint at 169.254.170.2 for cloud instance credentials. It also reads AWS credential environment variables including AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN. All harvested data is exfiltrated to the attacker-controlled webhook.site endpoint. The bundled npm client embedded in bundle.js published v4.0.2 automatically approximately 21 minutes later.
The timing of v4.0.1 -- 19:52 UTC on September 15, 2025 -- aligns exactly with the broader @ctrl wave, in which multiple packages across the namespace received identical modifications simultaneously.
Malicious Files
bundle.js is the 3.5 MB worm payload introduced by the attacker. SHA256: 46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09. It is byte-identical across all affected @ctrl packages in the September 15 wave.