How a stolen npm token turned legitimate packages into a self-spreading credential worm with an unkillable command server
The packages looked fine. Same name, same README, same metadata. Just a routine patch version bump, the kind that happens dozens of times a day across the npm registry. But the moment a developer or automated pipeline ran npm install, a 1,143-line script fired silently in the background, swept the machine for every credential it could find, and then used those credentials to infect every other package the developer had permission to publish.
That is the core of CanisterSprawl: a supply chain worm that turns each victim into the next attacker. One stolen token becomes dozens of infected packages. Dozens of infected packages reach thousands of downstream developers. Each of those developers potentially holds their own publishing tokens, and the cycle repeats. At least 22 npm packages across multiple publisher namespaces were confirmed compromised between April 8 and April 22, 2026, with new malicious versions still being published at the time of public disclosure.
The detail that should keep security teams awake: the worm exfiltrates stolen credentials to two destinations simultaneously. One is a conventional web server that defenders can ask a hosting provider to shut down. The other is a smart contract running on the Internet Computer Protocol (ICP) blockchain. That second channel cannot be seized by law enforcement, cannot be taken down by a hosting provider, and will remain active for the lifetime of the blockchain. The standard incident response playbook for supply chain attacks has no answer for it.
Narrative · 6 min read
The Context
npm is the package registry for JavaScript and Node.js, the most widely used programming language ecosystem in the world. Developers publish reusable code as packages, and other developers install those packages as dependencies in their own projects. Automated CI/CD pipelines (Continuous Integration/Continuous Deployment) run npm install thousands of times per day without any human reviewing what gets downloaded.
The trust model is simple: if a package comes from a known publisher account, it is treated as safe. That assumption is the structural flaw CanisterSprawl exploited.
The primary affected packages were @automagik/genie, published by Namastex Labs, with roughly 6,744 weekly downloads, and pgserve, with roughly 1,300 weekly downloads. Additional compromised namespaces included @fairwords/websocket, @fairwords/loopback-connector-es, @openwebconcept/design-tokens, and @openwebconcept/theme-owc. These are developer tools, meaning the machines that installed them were disproportionately likely to hold cloud credentials, CI/CD tokens, and registry publishing access.
The Attack, Phase by Phase
Phase 1: Package Compromise
The attacker obtained valid npm publishing tokens, most likely through a compromised CI/CD pipeline or a developer machine previously infected during the earlier CanisterWorm campaign in March 2026. With those tokens, the attacker replaced the contents of legitimate packages with malicious code and republished them as ordinary-looking patch version bumps, keeping original README files and metadata intact. The earliest confirmed activity dates to April 8, 2026. The first publicly timestamped malicious release of pgserve appeared April 21 at 22:14 UTC, with no corresponding Git tag in the upstream repository—a discrepancy only visible to someone actively comparing registry releases against source code commits.
Phase 2: Install-Time Credential Harvesting
When any developer or automated pipeline ran npm install on a compromised package, a 1,143-line script called check-env.js fired automatically via the postinstall hook. No user interaction was required.
The script swept the environment for: cloud provider credentials (AWS, GCP, Azure), CI/CD tokens, container registry credentials, LLM platform keys, SSH keys, .env files, .git-credentials files, .netrc files, Chromium browser data, and cryptocurrency wallet extensions including MetaMask, Phantom, Exodus, and Atomic Wallet.
Everything harvested was encrypted using AES-256, with the session key wrapped using an attacker-controlled RSA-4096 public key bundled inside the package at scripts/public.pem. Even intercepted outbound traffic was unreadable without the attacker's private key.
The encrypted payload was sent to two destinations simultaneously: a conventional HTTPS webhook at telemetry.api-monitor[.]com (a fresh domain with zero prior threat intelligence history) and an ICP blockchain canister at cjn37-uyaaa-aaaac-qgnva-cai.raw.icp0[.]io. The ICP canister is permanent—it cannot be seized, suspended, or taken down.
Phase 3: Worm Self-Propagation Across npm
After exfiltrating credentials, the script searched for npm publishing tokens in process.env.NPM_TOKEN and ~/.npmrc. For every package the stolen token could publish, it bumped the patch version, injected check-env.js and public.pem into a scripts/ directory, added the postinstall hook to package.json, and called npm publish --tag latest. The --tag latest flag ensures the infected version becomes the default install for any downstream user not pinning an exact version—which is the majority of automated pipelines.
A single compromised developer account could infect every package in their namespace. Each of those packages then reached their own downstream users, who held their own tokens, and the cycle continued.
Phase 4: Cross-Ecosystem Jump to PyPI
If harvested credentials included a PyPI token, the worm applied the same propagation logic to Python packages using .pth file injection: a file placed in the Python site-packages directory that executes on every Python interpreter invocation. The malicious payload contained an explicit code reference to the "TeamPCP/LiteLLM method" for .pth injection—a direct lineage marker connecting CanisterSprawl to the earlier CanisterWorm campaign targeting LiteLLM in March 2026.
What Made This Possible
-
Publisher identity is treated as permanent proof of safety. The npm registry cannot verify that the person publishing version 1.1.11 is the same person who published 1.1.10. A stolen token is indistinguishable from a legitimate one.
-
Postinstall hooks execute with no review gate. npm's postinstall hook runs arbitrary code automatically during install. There is no sandbox, no approval step, and no warning. The attack surface is the install command itself.
-
The exfiltration infrastructure was designed to outlast the response. The ICP canister cannot be taken down. The HTTPS domain was fresh with no threat intelligence history. By the time defenders identified the campaign, the attacker's infrastructure was already immune to the standard takedown playbook.
What Should Have Stopped This
No single control stops a worm that abuses legitimate registry features. The defenses that would have reduced the blast radius share one trait: they do not depend on the package registry's own integrity to function.
- Exact version pinning with lockfiles. Tools like
package-lock.jsonoryarn.lockpin every dependency to a specific, verified hash. A package published after the lockfile was generated cannot be installed without an explicit update step, breaking the worm's--tag latestpropagation mechanism. - Scoped, short-lived publishing tokens. A token that can only publish one specific package and expires after a short window limits any single compromise to that package. The worm's propagation loop depends on tokens with broad publish rights.
- Automated scanning at install time. Tools like Socket's AI scanner flagged
@automagik/genie@4.260421.36as malicious while it still had active download volume. Integrating registry scanning into CI/CD pipelines can catch compromised packages before they execute. - Principle of least privilege for CI/CD environments. A build pipeline that only needs to install packages should not hold a token that can also publish them. Separating install credentials from publish credentials removes the worm's ability to propagate through automated systems.
The Takeaway
CanisterSprawl is the same class of failure as the Axios build-time injection and the Stryker Intune wipe: a trusted system was weaponized against the organizations that depended on it. The meta-pattern across all three: systems fail when they trust a boundary the attacker controls. Here, that boundary was the publisher identity attached to an npm token.
What is new in CanisterSprawl is the self-amplifying cascade. Previous supply chain attacks compromised a package and waited for victims to install it. This one turned each victim into an active propagation vector, using their own credentials and their own trusted publisher identity to infect their own downstream users. The attack did not spread despite the trust model. It spread because of it.
The ICP canister adds a second new dimension. Incident response for supply chain attacks has always relied on killing the attacker's infrastructure: seize the domain, contact the hosting provider, get the server taken down. That playbook has no answer for a command channel running on a blockchain.
Pattern to remember: In a self-propagating supply chain worm, the victim's own trusted identity becomes the attack's delivery mechanism, and no downstream user has any signal that the infection came from someone they trusted.
What changed: For the first time in a documented supply chain worm, the attacker's command channel runs on a blockchain canister that is architecturally immune to takedown, removing infrastructure destruction as a reliable incident response step.
Technical Deep Dive · 4 min
The Technical Mechanism
CanisterSprawl operates as a three-stage build-time injection worm. The entry point is a compromised npm publishing token, most likely obtained through a CI/CD pipeline previously infected by the March 2026 CanisterWorm campaign or through direct credential theft from a developer machine.
Payload delivery: Malicious versions of legitimate packages include two injected files: scripts/check-env.js (1,143 lines) and scripts/public.pem (attacker-controlled RSA-4096 public key). The package.json scripts.postinstall field is set to node scripts/check-env.js, triggering execution on every npm install invocation.
Credential harvesting scope: check-env.js enumerates process.env for tokens matching patterns for AWS (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY), GCP, Azure, GitHub Actions (GITHUB_TOKEN), CircleCI, npm (NPM_TOKEN), PyPI, Docker, Kubernetes, and LLM platforms. It also reads ~/.npmrc, ~/.pypirc, ~/.git-credentials, ~/.netrc, .env files in the working directory, Chromium-based browser profile data (cookies, local storage), and cryptocurrency wallet extension data for MetaMask, Phantom, Solana, Ethereum, Bitcoin, Exodus, and Atomic Wallet.
Encryption: Harvested data is serialized to JSON, encrypted with AES-256-CBC using a randomly generated session key, and the session key is then encrypted with the RSA-4096 public key from scripts/public.pem. The resulting ciphertext is base64-encoded and exfiltrated. Without the attacker's RSA private key, the payload cannot be decrypted regardless of interception.
Exfiltration channels:
- HTTPS POST to
telemetry.api-monitor[.]com(registered via Bluehost with privacy protection, zero prior threat intelligence history at time of disclosure) - HTTP POST to ICP canister
cjn37-uyaaa-aaaac-qgnva-cai.raw.icp0[.]iovia the Internet Computer Protocol's HTTP gateway
Propagation (npm): The script reads process.env.NPM_TOKEN and parses ~/.npmrc for _authToken entries. For each token found, it calls the npm registry API to enumerate packages the token can publish. For each package, it: (1) fetches the current latest tarball, (2) unpacks it, (3) injects check-env.js and public.pem into scripts/, (4) modifies package.json to add the postinstall hook and increment the patch version, (5) repacks the tarball, and (6) calls npm publish --tag latest.
Propagation (PyPI): If a PyPI token is found, the worm locates the Python site-packages directory and writes a .pth file containing a Python exec() call to a harvesting payload. .pth files in site-packages are processed by the Python interpreter on startup, providing both persistence and further propagation. The payload contains an explicit string reference to "TeamPCP/LiteLLM method" for this technique.
ICP canister architecture: The ICP canister exposes HTTP endpoints via the Internet Computer's HTTP gateway (raw.icp0.io). Unlike a conventional web server, the canister's code and state are replicated across the ICP blockchain's node network. There is no single host to seize, no domain to suspend at the registrar level, and no hosting provider to contact. The canister ID cjn37-uyaaa-aaaac-qgnva-cai is permanent for the lifetime of the ICP blockchain. The earlier CanisterWorm campaign used a different canister ID, suggesting the attacker deploys fresh canisters per campaign.
No CVE applies to this attack. The vulnerability is not a software defect but a design property of the npm postinstall hook system combined with the absence of token scope enforcement at the registry level.
CVE and Advisories
No CVE has been assigned for CanisterSprawl. The attack does not exploit a software vulnerability. It abuses legitimate npm registry features (postinstall hooks and publishing tokens) in combination with stolen credentials. No formal vendor advisory has been issued by npm or GitHub as of April 22, 2026.
MITRE ATT&CK Mapping
| Technique ID | ATT&CK name | How it appeared |
|---|---|---|
| T1195.001 | Supply Chain Compromise: Compromise Software Dependencies and Development Tools | Attacker injected malicious code into legitimate npm packages by publishing new versions using stolen publishing tokens. |
| T1059.007 | Command and Scripting Interpreter: JavaScript | check-env.js executed via npm postinstall hook to harvest credentials and trigger propagation. |
| T1552.001 | Unsecured Credentials: Credentials in Files | Script reads ~/.npmrc, ~/.git-credentials, ~/.netrc, .env files, and browser profile data for stored secrets. |
| T1552.004 | Unsecured Credentials: Private Keys | SSH keys and cloud provider key files harvested from the victim filesystem. |
| T1041 | Exfiltration Over C2 Channel | Encrypted credential payload exfiltrated to HTTPS webhook and ICP blockchain canister simultaneously. |
| T1567 | Exfiltration Over Web Service | ICP canister used as a decentralized, seizure-resistant exfiltration endpoint. |
| T1554 | Compromise Client Software Binary | Worm republishes infected versions of packages the stolen token can reach, replacing legitimate releases. |
| T1176 | Browser Extensions | Chromium browser data and cryptocurrency wallet extension data harvested from victim machines. |
Indicators of Compromise
Network Indicators
telemetry.api-monitor[.]com(HTTPS exfiltration webhook, registered via Bluehost with privacy protection)cjn37-uyaaa-aaaac-qgnva-cai.raw.icp0[.]io(ICP canister exfiltration endpoint)
Both domains were added to the StepSecurity Harden-Runner global block list on April 22, 2026. The ICP canister endpoint cannot be taken down; blocking at the network egress layer is the only available mitigation for that channel.
File Indicators
scripts/check-env.jspresent in an npm package's installed directory (1,143 lines, credential harvesting script)scripts/public.pempresent in an npm package's installed directory (RSA-4096 public key for payload encryption)- Postinstall hook in
package.jsonpointing tonode scripts/check-env.js .pthfiles in Pythonsite-packagescontainingexec()calls (cross-ecosystem propagation)
Package Indicators
Confirmed malicious versions as of April 22, 2026:
pgserveversions1.1.11,1.1.12,1.1.13(last legitimate release:v1.1.10, tagged April 17, 2026)@automagik/genieversions4.260421.33through4.260421.39@fairwords/websocketversions1.0.38,1.0.39@fairwords/loopback-connector-esversions1.4.3,1.4.4@openwebconcept/design-tokens(specific versions under investigation)@openwebconcept/theme-owc(specific versions under investigation)
Detection is complicated by the fact that malicious versions retain original README files and metadata. Version number comparison against upstream Git tags is the most reliable detection signal: malicious releases have no corresponding Git tag in the source repository.
Attribution
Attribution is split between the two detecting firms. StepSecurity attributes CanisterSprawl directly to TeamPCP. Socket describes the campaign as "TeamPCP-style" and notes strong overlap in technique and code lineage but stops short of confident attribution, observing that the ICP canister ID used in CanisterSprawl (cjn37-uyaaa-aaaac-qgnva-cai) differs from the canister documented in the original CanisterWorm campaign.
Wiz Research previously attributed the original CanisterWorm campaign to TeamPCP, linking the group to the March 2026 compromises of Aqua Security Trivy, Checkmarx KICS, LiteLLM, and the Telnyx Python SDK. Palo Alto Unit 42 describes TeamPCP as having roots in ransomware and cryptocurrency theft before pivoting to supply chain compromise operations in mid-March 2026, and notes announced partnerships with the CipherForce and Vect ransomware groups on BreachForums.
The explicit "TeamPCP/LiteLLM method" string inside the CanisterSprawl payload is the strongest public code lineage marker. No nation-state link has been publicly asserted by any credible threat intelligence firm as of April 22, 2026.
Primary Sources
- 01.
- 02.Namastex.ai npm Packages Hit with TeamPCP-Style CanisterWorm Tradecraft
Socket · April 22, 2026
- 03.Self-Propagating Supply Chain Worm Hijacks npm Packages to Steal Developer Tokens
The Hacker News · April 22, 2026
- 04.Another npm supply chain worm hits dev environments
The Register · April 22, 2026
- 05.Namastex npm packages compromised in CanisterWorm supply chain attack
SC Media · April 22, 2026
- 06.No Off Season: Three Supply Chain Campaigns Hit npm, PyPI, and Docker Hub in 48 Hours
GitGuardian · April 23, 2026
- 07.Weaponizing the Protectors: TeamPCP's Multi-Stage Supply Chain Attack on Security Infrastructure
Palo Alto Unit 42 · April 2026
- 08.CanisterWorm: The Self-Spreading npm Attack That Uses a Decentralized Server to Stay Alive
Mend.io · March 21, 2026