Introduction
This vulnerability is a direct consequence of the Trivy supply chain attack. After compromising Trivy's infrastructure, TeamPCP used it as a launchpad to deploy a self-propagating worm that spread through the npm ecosystem. The worm steals publish tokens, injects itself into packages, persists through reboots via systemd, and uses the ICP (Internet Computer Protocol) blockchain as an uncensorable command-and-control channel.
Attack Chain
The attack unfolds in five stages, each building on the previous one to expand the worm's reach.
Stage 1 — Token Harvesting via Trivy
TeamPCP's compromised Trivy infrastructure — trivy-action, setup-trivy, and the infected binary — was already running inside CI/CD pipelines worldwide. The malware exfiltrated environment variables from these pipelines, specifically targeting NPM_TOKEN values. The infected binary communicated with a typosquatted endpoint (scan.aquasecurtiy.org, note the misspelling) to receive stolen credentials.
Every repository that ran the compromised Trivy tooling during the attack window had its npm publish tokens stolen.
Stage 2 — Persistence via systemd
Once executed, the worm writes a Python backdoor (service.py) to disk and installs a systemd user service (pgmon.service) configured with Restart=always. This means the backdoor:
- Survives process termination
- Survives reboots
- Persists indefinitely until manually discovered and removed
Stage 3 — Blockchain-Based Command and Control
The systemd service begins polling an ICP (Internet Computer Protocol) canister — a smart contract running on the Internet Computer blockchain. ICP was chosen deliberately: it is decentralized with no central authority, making it effectively impossible to take down or file complaints against. There is no registrar to contact, no hosting provider to issue an abuse report to.
The canister serves malicious binaries that the worm downloads and executes as second-stage payloads.
Stage 4 — Self-Propagation Through npm
A deploy.js script is also written to disk. This script:
- Gathers all npm publish tokens available in the environment
- Identifies which packages those tokens have publish access to
- Injects the malicious payload into each package
- Publishes the infected version with the
latesttag
Anyone who installs an infected package (via npm install package-name, which defaults to latest) gets compromised. Their environment then becomes another propagation point — the worm harvests their tokens and infects any packages they can publish to. This creates an exponential spread pattern across the npm ecosystem.
Stage 5 — Destructive Payloads
The second-stage binary downloaded from the ICP canister fingerprints the host environment and selects its behavior accordingly:
- Kubernetes clusters detected: Attempts to delete clusters and workloads
-
General Linux systems: Executes
rm -rf --no-preserve-root /, destroying the entire filesystem - Other environments: Remains as a passive backdoor, awaiting further instructions from the C2 canister
How to Check If You're Compromised
Indicators of Compromise (IoCs)
Check for the presence of the following artifacts on any machine that ran the compromised Trivy tooling or installed a potentially infected npm package:
| Artifact | Path | Significance |
|---|---|---|
| Python implant | ~/.local/share/pgmon/service.py |
Confirms malware executed |
| systemd persistence | ~/.config/systemd/user/pgmon.service |
Confirms persistence was established |
| Second-stage binary | /tmp/pglog |
Confirms C2 delivery occurred |
| State tracker | /tmp/.pg_state |
Confirms download state tracking |
Other Signs
- Unexpected references to
scan.aquasecurtiy.org(typosquatted domain) in logs or network traffic - Unknown ICP canister endpoints in your code or environment
- Unexplained
postinstallscripts in packages you maintain - npm package versions published that you did not author
Remediation: What to Do If You're Affected
-
Confirm execution: Check for the presence of
~/.local/share/pgmon/service.pyand~/.config/systemd/user/pgmon.service. Their existence is proof that the malicious code ran on your system. -
Stop the persistence service:
systemctl --user stop pgmon.service systemctl --user disable pgmon.service rm ~/.config/systemd/user/pgmon.service rm -rf ~/.local/share/pgmon/ rm -f /tmp/pglog /tmp/.pg_state systemctl --user daemon-reload -
Rotate npm tokens immediately: Revoke and regenerate every npm token that was present in the environment or on the machine where the malware executed.
-
Rotate all other secrets: CI/CD secrets, cloud credentials, SSH keys, API tokens, and any other sensitive values present in the compromised environment should be assumed stolen and rotated.
-
Audit your published packages: Check if any packages you maintain had unauthorized version bumps or unexpected
postinstallscripts added. If so, unpublish the malicious versions and publish clean replacements. -
Pin package versions: Update your
package.jsonand lock files to pin dependencies to known safe versions rather than relying onlatest.