CanisterWorm: A Self-Propagating npm Worm Is Stealing Developer Tokens and Spreading Autonomously
Introduction
Socket and StepSecurity have disclosed a new round of compromised npm packages carrying a self-propagating supply chain worm tracked as CanisterWorm (also referred to as CanisterSprawl). The malware uses an Internet Computer Protocol (ICP) blockchain canister as its dead-drop command-and-control channel, steals npm publishing tokens from every infected workstation and CI runner, and uses those tokens to automatically publish backdoored versions of whatever packages the victim has access to. The campaign has now expanded to include Namastex Labs' @automagik scope and others, with the malicious @automagik/genie release still recording roughly 6,700 weekly downloads when Socket flagged it.
What Happened
CanisterWorm was first observed as a cluster of suspicious patch releases under the legitimate @emilgroup npm scope in March 2026, eventually spreading to more than 135 malicious package artifacts across 64+ packages. Public reporting attributes the campaign to the threat group known as TeamPCP — the same actor behind the Trivy and Checkmarx GitHub Action compromises.
The infection chain is triggered by npm's postinstall lifecycle script. When a developer or CI runner installs a compromised package, the loader runs automatically and performs four operations:
- Credential harvesting — The payload reads npm tokens from
~/.npmrc, project-level.npmrcfiles,/etc/npmrc, theNPM_TOKENandNPM_TOKENSenvironment variables, and livenpm config getqueries. It also scrapes cloud credentials, SSH keys, Git PATs, database URIs, and cryptocurrency wallet artifacts (MetaMask, Phantom, Exodus, Atomic Wallet, Ethereum, Bitcoin keystores). - Exfiltration — Collected data is encrypted with a hybrid AES-256-CBC and RSA-OAEP-SHA256 scheme using an embedded RSA public key, then sent to both an HTTPS webhook and an ICP canister endpoint.
- Persistence — On Linux the loader installs a
systemd --userservice disguised as PostgreSQL tooling (~/.local/share/pgmon/service.py), surviving logout and reboots without needing root. - Self-propagation — With npm tokens in hand, a worm component resolves the associated usernames through npm's
/-/whoami, enumerates every package the victim can publish to, injects a maliciouspostinstallhook, and republishes a tainted version.
The use of an ICP canister is what makes the campaign especially resilient. A canister has no central server to seize, no domain registrar to abuse-complaint, and supports an update_link method that lets the attacker rotate the payload at will. The current canister endpoints include tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io (original) and cjn37-uyaaa-aaaac-qgnva-cai.raw.icp0.io/drop (newer Namastex variant).
Among the packages confirmed compromised in the current wave:
@emilgroup/account-sdk,@emilgroup/account-sdk-node,@emilgroup/public-api-sdk,@emilgroup/public-api-sdk-node,@emilgroup/translation-sdk-node@teale.io/eslint-config@automagik/genie@fairwords/websocket
Why It Matters
A worm that spreads through stolen npm publishing tokens is an asymmetric weapon. Every new developer or CI pipeline that installs a compromised package becomes both a new victim and a new propagation node — the infected footprint grows without attacker intervention. Because the persistence uses user-level systemd (no root needed), most EDR policies and hardening baselines miss it. And because the C2 runs on a blockchain canister, traditional takedown playbooks do not apply.
For engineering teams, the risk is double: your build pipelines may be serving the worm to your customers, and your developer workstations may be harvesting credentials that give attackers lateral access into your cloud.
Who Is Affected
- Any organization that installed
@emilgroup/*,@teale.io/eslint-config,@automagik/*,@fairwords/*, or related packages since March 2026 - Linux developer workstations and CI runners running Node.js installs
- npm publishers whose tokens were present in CI environments during the exposure window
- Downstream consumers of any package published by a compromised maintainer
How to Protect Yourself
Check for worm artifacts on Linux workstations and CI runners:
ls -la ~/.local/share/pgmon/service.py 2>/dev/null
systemctl --user status pgmon 2>/dev/null
ls -la /tmp/pglog /tmp/.pg_state 2>/dev/null
Block and hunt the C2 domains at your DNS and egress firewall:
tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io
cjn37-uyaaa-aaaac-qgnva-cai.raw.icp0.io
scan.aquasecurtiy.org # typosquat — note the missing 'i'
telemetry.api-monitor.com
Review proxy and DNS logs for any connection to *.icp0.io from developer machines or CI runners — legitimate Node.js development rarely needs to talk to the Internet Computer.
Audit your package-lock files for suspicious recent patch versions of the named scopes:
grep -E '@(emilgroup|teale\.io|automagik|fairwords)' package-lock.json yarn.lock pnpm-lock.yaml
If any match, remove the compromised versions and reinstall from a known-good version.
Disable postinstall scripts in CI pipelines where you do not actually need them:
npm ci --ignore-scripts
# or globally:
npm config set ignore-scripts true
Rotate every npm token that was present on any machine that ran a compromised package. Invalidate via the npm website or CLI:
npm token list
npm token revoke <TOKEN_ID>
Then rotate all other secrets that were accessible alongside the npm tokens — AWS keys, GitHub PATs, SSH keys, Docker Hub credentials, database URIs.
Use short-lived, scoped npm automation tokens going forward. Prefer npm's OIDC-based trusted publishing over long-lived NPM_TOKEN values in CI:
permissions:
id-token: write
contents: read
steps:
- uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
- run: npm publish --provenance --access public
Install a policy to detect worm-style republishing. StepSecurity, Socket, Phylum, and Snyk all offer detections for CanisterWorm — wire one into your pipeline to fail builds that pull a flagged release.