Advisory

TeamPCP Hackers Deploys CanisterWorm Supply Chain Malware via Compromised NPM Packages

Take action: If you use Trivy GitHub Actions or any npm packages from the affected scopes (@EmilGroup, @opengov, @teale.io, @airtm), assume your credentials are compromised — immediately rotate all secrets including npm tokens, SSH keys, cloud credentials, and Kubernetes tokens. Check your systems for suspicious services named "pgmon," "internal-monitor," or "pgmonitor," unexpected DaemonSets in kube-system, and files in /tmp/pglog or /var/lib/pgmon/, and remove anything suspicious immediately.


Learn More

On March 20, 2026, at 20:45 UTC, Aikido Security researcher Charlie Eriksen detected a large-scale compromise across the NPM registry involving a previously undocumented self-propagating worm dubbed "CanisterWorm," deployed by the cloud-native threat actor group known as TeamPCP. 

The attack followed less than 24 hours after TeamPCP compromised Aqua Security's widely used Trivy vulnerability scanner, injecting credential-stealing malware into official releases and GitHub Actions. TeamPCP exploited a GitHub Actions misconfiguration involving a pull_request_target workflow that exposed a Personal Access Token (PAT), which was then used to force-push malicious commits over 75 of 76 version tags on aquasecurity/trivy-action and 7 tags on aquasecurity/setup-trivy, effectively replacing the legitimate scanner with a credential harvester across thousands of CI/CD pipelines. 

The stolen npm authentication tokens from that operation became the launchpad for CanisterWorm's initial wave, which compromised 28 packages in the @EmilGroup scope, 16 packages in the @opengov scope, and additional packages including @teale.io/eslint-config and @airtm/uuid-base32. By March 21, the attack had expanded to more than 135 malicious package artifacts spanning over 64 unique packages. 

CanisterWorm employs a three-stage architecture that represents a significant escalation in supply chain attack sophistication. 

  1. The infection begins with a Node.js postinstall hook that silently decodes and drops a Python backdoor to the victim's system, then registers it as a systemd user-level service disguised as PostgreSQL tooling (named "pgmon") with Restart=always to ensure persistence across reboots and crashes, all without requiring root privileges.
  2. The Python backdoor then polls an Internet Computer Protocol (ICP) canister:  a tamperproof smart contract on a decentralized blockchain network acting as a dead-drop command-and-control channel, making the C2 infrastructure resistant to conventional takedown efforts.
  3. The canister returns a URL pointing to the next-stage binary payload, which is downloaded to /tmp/pglog and executed. A built-in kill switch causes the backdoor to skip execution if the returned URL contains "youtube.com," allowing the attacker to arm or disarm the implant at will. Critically, the attacker can rotate payloads at any time by updating the canister, pushing new binaries to all infected machines without ever modifying the compromised packages. The worm component (deploy.js) takes stolen npm tokens, resolves the associated usernames, enumerates all publishable packages, automatically bumps patch versions, preserves original READMEs to maintain appearances, and publishes the malicious payload with --tag latest to ensure it becomes the default installation. 

An upgraded variant detected in @teale.io/eslint-config versions 1.8.11 and 1.8.12 added a findNpmTokens() function that harvests npm tokens from .npmrc files, environment variables, and npm config during postinstall, then automatically spawns the worm — making every compromised developer or CI pipeline an unwitting propagation vector.

On March 22, 2026, Aikido Security reported an escalation in the campaign: a new payload discovered in the TeamPCP arsenal that wipes entire Kubernetes clusters, using the same ICP canister C2 infrastructure documented in the initial CanisterWorm campaign. 

This variant introduces a geopolitically targeted destructive capability aimed specifically at Iranian systems. The payload checks the system timezone (for "Asia/Tehran" or "Iran") and locale (for "fa_IR") to determine its target, then follows a branching decision tree with four paths. On Kubernetes clusters identified as Iranian, it deploys a privileged DaemonSet called "host-provisioner-iran" containing a container named "kamikaze" that mounts the host's root filesystem, deletes everything at the top level, and force-reboots — effectively bricking every node in the cluster including the control plane. On non-Iranian Kubernetes clusters, it instead deploys a DaemonSet ("host-provisioner-std") that installs the CanisterWorm backdoor as a systemd service across all nodes. On non-Kubernetes Iranian systems, it executes rm -rf / --no-preserve-root to destroy the host. Non-Iranian, non-Kubernetes systems are simply ignored.

A third iteration of the payload added network-based lateral movement capabilities that no longer depend on Kubernetes. This variant parses SSH authentication logs (/var/log/auth.log and /var/log/secure) to extract successful login pairs (usernames and IPs), steals SSH private keys from ~/.ssh/, and uses them to spread to other machines on the local /24 subnet. It also scans for exposed Docker APIs on port 2375, creating privileged Alpine containers with the host root mounted to deliver the same payload. Both spread methods carry the Iran timezone check — Iranian targets are wiped, while all others receive the persistent CanisterWorm backdoor. The payload delivery infrastructure rotated through multiple Cloudflare tunnel domains, and the PostgreSQL-themed camouflage evolved across iterations, with service names moving from "pgmon" to "internal-monitor" to "pgmonitor" and install paths shifting accordingly.

Organizations that ran Trivy-related GitHub Actions between March 19 and March 21, 2026 should treat all accessible secrets as potentially compromised and rotate them immediately, including SSH keys, cloud provider credentials, Kubernetes tokens, Docker configs, and npm authentication tokens. Organizations should evaluate whether their dependency trees include packages from the affected scopes identified in threat intelligence reporting.

Kubernetes administrators should check for unexpected DaemonSets in the kube-system namespace by running kubectl get ds -n kube-system and looking for "host-provisioner-iran" or "host-provisioner-std," and should audit any DaemonSet that mounts hostPath: / with a privileged security context. On the host side, defenders should look for systemd services named pgmon, internal-monitor, or pgmonitor; files at /tmp/pglog, /tmp/.pg_state, /var/lib/pgmon/, or /var/lib/svc_internal/; and outbound connections to icp0.io domains or port 2375 across the local subnet. 

TeamPCP Hackers Deploys CanisterWorm Supply Chain Malware via Compromised NPM Packages