# CanisterWorm: How a Self-Propagating npm Worm Is Spreading Backdoors Across the Ecosystem

TeamPCP's CanisterWorm uses stolen npm tokens to republish backdoored packages whose postinstall installs a persistent Python backdoor polling an ICP canister C2.

- Payout: Real-world hack
- Severity: critical
- Program: npm ecosystem
- Analysis by: StepSecurity
- Disclosed: 2026-03-26
- Write-up quality: 7/10
- Original: https://www.stepsecurity.io/blog/canisterworm-how-a-self-propagating-npm-worm-is-spreading-backdoors-across-the-ecosystem
- Library page: https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-canisterworm-how-a-self-propagating-npm-worm-is-spreading-backdoors-across-the-ecosystem
- Tags: area:oss domain:oss impact:data-leak impact:rce kind:incident platform:threat-intel sev:critical tech:ci-cd tech:github-actions tech:nodejs technique:chained technique:source-review vuln:misconfiguration vuln:secrets-leak

**Target:** npm packages / CI-CD pipelines
**Root cause:** Stolen npm publish tokens from the compromised Trivy v0.69.4 CI/CD toolchain let the attacker inject a postinstall hook into patch versions of every reachable package, which installs a systemd user service backdoor and harvests further tokens to self-propagate.
**Steps:**
1. Compromise Trivy v0.69.4 and trivy-action/setup-trivy to harvest CI secrets incl. NPM_TOKEN via /proc/<pid>/mem
2. Use stolen token to bump patch version and add `"postinstall": "node index.js"` to package.json
3. index.js decodes hardcoded BASE64_PAYLOAD and writes `~/.local/share/pgmon/service.py` plus `~/.config/systemd/user/pgmon.service` (Restart=always)
4. service.py polls `https://tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io/` every ~50 min, drops stage-2 to `/tmp/pglog`, tracks state in `/tmp/.pg_state`
5. deploy.js harvests tokens from ~/.npmrc, /etc/npmrc, NPM_TOKEN env, `npm config get`, enumerates publishable scopes, republishes with `--tag latest`
6. Stage-2 deploys privileged DaemonSet host-provisioner-iran on K8s or runs `rm -rf / --no-preserve-root` on non-K8s Iranian targets
**How found:** StepSecurity's AI Package Analyst diffed new npm publishes against full release history and flagged first-ever postinstall scripts plus a base64-encoded payload in index.js; decoding revealed the C2 URL and findNpmTokens() harvester, and the same fingerprint across multiple scopes indicated a coordinated worm rather than a single account compromise.
**Exploitation:** Attacker first compromised Trivy's release and GitHub Actions to exfiltrate NPM_TOKENs from any pipeline running them, then used those tokens to publish backdoored patch versions across reachable npm scopes; every install executes the postinstall, installs a systemd user service, and the worm re-harvests tokens to republish to further packages, while the ICP-hosted C2 delivers destructive K8s DaemonSets or filesystem wipes to targeted victims.
**Escalation:** Worm self-propagates via harvested npm tokens to every publishable package; stage-2 payload escalates from persistence to cluster-wide destructive DaemonSet or host filesystem wipe for geopolitically targeted victims.
**Insight:** Diff every new package version against its own release history for first-time install scripts and embedded base64 blobs — decoding the blob exposes C2 and token-harvesting logic that static scanners miss; identical fingerprints across unrelated scopes signal a worm, not an account takeover.
