R’member that thing I wrote about n8n’s workflow automation RCE sitting on 24,700 exposed instances and thinking, okay, that’s a bad week for people who build things. Then my feed served up the full picture on TeamPCP’s ongoing massacre of Aqua Security’s Trivy project and I’m going to need a second coffee. Maybe a third. Because this one is special — not “critical CVSS score” special, but “the tool you pay to find vulnerabilities in your software was secretly cataloguing your cloud credentials for attackers” special. The scanner got scanned. Twice. And then it spawned a worm.
What Happened
Between late February and March 22, a threat actor self-identifying as TeamPCP executed a multi-stage supply chain attack against Aqua Security’s Trivy, the most widely-deployed open-source vulnerability scanner in the cloud-native world. Over 32,000 GitHub stars. A hundred million Docker Hub pulls. Integrated into tens of thousands of CI/CD pipelines precisely because engineers trust it to make their code safer.
The first compromise came earlier in March when TeamPCP exfiltrated credentials from Trivy’s CI environment. Aqua rotated those credentials. They apparently didn’t rotate fast enough, because per Wiz Research’s forensic reconstruction, TeamPCP retained access to newly issued credentials. On March 19, they came back.
This time they force-pushed malicious commits to 75 of 76 version tags on aquasecurity/trivy-action and aquasecurity/setup-trivy, published a backdoored Trivy binary (version 0.69.4) to GitHub Releases, Docker Hub, GHCR, and ECR, and impersonated maintainers by spoofing user identities including rauchg and DmitriyLewen to make the malicious commits look legitimate. The malware — TeamPCP’s own “Cloud Stealer,” documented by Wiz Research and Socket Security researcher Philipp Burckhardt — dumps the Runner.Worker process memory, harvests SSH keys, AWS/GCP/Azure credentials, Kubernetes service account tokens, Docker registry credentials, database passwords, npm authentication tokens, shell histories, and TLS private keys. It encrypts the haul with AES-256 plus RSA-4096 and exfiltrates it to a Cloudflare Tunnel C2. For a fallback, it creates a repository named tpcp-docs in the victim’s GitHub account. Subtle.
Then it got worse. Per Aikido Security researcher Charlie Eriksen, who was the first to name CanisterWorm, TeamPCP used those stolen npm tokens within 24 hours to push malicious versions to over 50 npm packages across scopes including @EmilGroup, @opengov, @teale.io, @airtm, and @pypestream. The malware installs a persistent backdoor via a postinstall hook that writes a systemd user service named pgmon — disguised as PostgreSQL monitoring tooling — that runs on every reboot and reconnects to a command-and-control server. Here’s where it gets structurally interesting in the worst possible way: the C2 is an ICP canister. An Internet Computer Protocol smart contract. Decentralised blockchain infrastructure. Endor Labs head of security research Henrik Plate characterised the technique accurately — the worm uses stolen npm tokens to immediately republish the malicious code to the victim’s own packages, turning a single compromise into an exponential propagation chain.
You cannot seize a blockchain node. You cannot serve a court order to a smart contract. The canister persists as long as the chain exists, which is by design indefinitely. Mend.io’s analysis confirms that while the current URL returned by the C2 is a YouTube rickroll, the architecture has three callable methods — including update_link — that let TeamPCP hot-swap the payload to any target across every infected machine simultaneously, with no update to the implant itself.
As of March 22, TeamPCP leveraged a compromised “Argon-DevOps-Mgt” service account — a single bot account bridging Aqua’s two GitHub organisations with a long-lived personal access token — to deface all 44 repositories in Aqua’s internal aquasec-com org in a scripted 2-minute burst, renaming each with a tpcp-docs- prefix and setting descriptions to “TeamPCP Owns Aqua Security.” And on the same day, Infosecurity Magazine reported new malicious Trivy Docker images 0.69.5 and 0.69.6 were pushed to Docker Hub without corresponding GitHub releases. The ARMO security team’s analysis, published this morning, confirms this campaign ran across two successive supply chain compromises of the same project, with the second wave accessing credentials that survived incomplete containment of the first.
ShinyHunterz walked off with a petabyte of TELUS Digital’s data last week using a similar playbook of credential theft at cloud scale, but CanisterWorm introduces a nastier dimension: self-replication through the victim’s own publishing infrastructure.
Why It Matters
Over 10,000 GitHub workflows directly reference aquasecurity/trivy-action. That’s 10,000 pipelines that, at some point between March 19 and March 22, were potentially executing TeamPCP’s Cloud Stealer against their own environments. Every secret in those pipelines — AWS access keys, service account credentials, npm tokens, Docker registry passwords — was fair game for exfiltration.
The downstream blast radius from CanisterWorm is still expanding. Any developer or CI/CD runner that installed one of the compromised npm packages also had its npm tokens stolen and was silently enrolled as a propagation node, infecting every package those tokens could publish to. The secondary mutation discovered in @teale.io/eslint-config versions 1.8.11 and 1.8.12 takes this further: it doesn’t even need manual intervention to spread. It propagates autonomously on install.
The security vendor ecosystem itself is now a preferred attack surface. TeamPCP didn’t go after a bank or a healthcare system. They went after the security tooling that sits at the root of trust for cloud-native development environments. My research on credential supply chain attacks and the dark web infrastructure that enables them goes back years on exactly this pattern: the most dangerous attack vector is always the one that wears a trusted identity.
What Went Wrong
Two things. One is technical and one is institutional.
Technical: Aqua rotated credentials after the first compromise and believed the access paths were closed. They were not. The attacker retained access to newly issued credentials — most likely through a service account token that wasn’t in scope for the initial rotation. The Argon-DevOps-Mgt bot account bridged two GitHub organisations with a long-lived PAT. One token. Two orgs. Total visibility into both. That’s not a bug. That’s an architecture decision that aged like milk.
Institutional: The security industry builds and deploys critical tooling with the same level of supply chain diligence it recommends customers apply to their own software — which is to say, not nearly enough. Version tags in GitHub Actions can be force-pushed by anyone with repo write access. Most organisations pin their CI/CD actions to version tags, not immutable commit hashes. This means a compromised maintainer account is all that stands between your pipeline and complete credential exfiltration. The fact that Trivy’s maintainers hadn’t addressed this after the first compromise is less a failing of the Aqua team specifically and more a structural failure of how the industry thinks about trust in open-source tooling.
The Fix — Fixer’s Advice
Immediate triage (do this now, before anything else):
If your CI/CD pipelines used aquasecurity/trivy-action, aquasecurity/setup-trivy, or any Trivy Docker image between March 19 and March 22, 2026, treat every secret that was present in those environments as fully compromised. That’s not a precaution. That’s a reasonable forensic conclusion based on what the malware was designed to collect. Start rotating immediately.
Rotation checklist — in priority order:
- AWS IAM keys, GCP service accounts, Azure service principals — anything with cloud provider access. Revoke first, re-issue second. Do not swap; revoke and issue fresh. Check CloudTrail and equivalent audit logs for anomalous API calls from those credentials in the past 72 hours.
- GitHub personal access tokens and fine-grained tokens used by any service accounts or bot accounts connected to repos that ran the compromised actions. The Argon-DevOps-Mgt pattern — a single long-lived PAT bridging multiple orgs — is exactly the credential class TeamPCP weaponised. Every long-lived PAT in your org should be audited right now.
- npm authentication tokens — check
.npmrcfiles in developer home directories and CI/CD runner environments. If any token was present on a machine that ran a compromised Trivy action or installed a CanisterWorm-infected package, revoke it immediately through the npm registry. Audit recent publish events on any packages those tokens had access to. - Kubernetes service account tokens, Docker registry credentials, and database connection strings present in any affected CI environment.
- SSH keys — check
~/.ssh/across developer machines that installed affected packages.
Persistence removal (CanisterWorm-specific):
If any developer machine or CI runner installed a compromised npm package, look for the pgmon systemd user service:
- Check
~/.config/systemd/user/pgmon.service - Check
~/.local/share/pgmon/service.py - Disable with:
systemctl --user stop pgmon && systemctl --user disable pgmon - Delete both the service unit file and the Python backdoor
The service runs without root. Standard EDR tools may not flag a user-mode systemd service with a postgres-sounding name. Look explicitly.
GitHub Actions hardening (do this organisation-wide, now):
Pin every third-party GitHub Action you use to an immutable commit SHA, not a version tag. Version tags can be force-pushed by anyone with repository write access. A commit SHA cannot be moved. Example:
Instead of: uses: aquasecurity/[email protected]
Use: uses: aquasecurity/trivy-action@<FULL_COMMIT_SHA>
Use tools like pin-github-action or ratchet to automate this across your workflows. It’s a one-time investment with permanent protection against tag-based supply chain compromise.
Service account architecture review:
Audit every bot account and service account in your GitHub organisations. Any account with cross-org access via a long-lived PAT is a single point of failure for both organisations. Migrate to short-lived OIDC tokens for cloud provider authentication instead of static credentials stored as repository secrets. GitHub Actions supports OIDC federation with AWS, GCP, and Azure. If you’re still using long-lived IAM keys as repository secrets, the Argon-DevOps-Mgt incident should be all the justification you need to fix that this week.
npm package audit:
Run npm audit across your project dependencies. Check whether any packages you depend on were among the compromised scopes. Endor Labs and JFrog both have up-to-date indicators of compromise from the CanisterWorm campaign. Subscribe to their feeds.
Detection going forward:
Behaviour-based detection is what catches this class of attack before the forensics report arrives. Specifically: flag any postinstall scripts that write systemd unit files, create files outside standard temp directories, or make outbound HTTP connections to non-standard ports or callback services like oastify.com, interact.sh, or dnslog.cn. Most organisations don’t have this visibility into CI/CD runners. Get it.
Final Call-Out
The tool you trust to find your secrets just found them for someone else. That’s not a metaphor. That’s the campaign log. TeamPCP didn’t need a zero-day. They needed a compromised maintainer credential, a long-lived PAT someone forgot to scope properly, and 24 hours. A year from now, when the next supply chain worm shows up, the question will be the same one it always is: was your CI/CD infrastructure built with the assumption that the tools inside it could be turned against you? If the answer is no, start fixing it today, not after the breach notification.
