March 31, 2026
6
min

TeamPCP's LiteLLM Takeover: A Cascading Supply Chain Attack Across Five Ecosystems

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.
Petr Zuzanov
Principal Security Researcher
No items found.
No items found.

TL;DR

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.

The Campaign: From Trivy to LiteLLM in 5 Days

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:

  • Mar 19, 17:43 UTC: Trivy CI/CD compromised - attacker pushes 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-pushed
  • Mar 20: Wiz publishes initial Trivy disclosure. Aqua Security begins cleanup - but fails to fully revoke attacker access
  • Mar 22, ~13:15 UTC: Wiz identifies parallel compromise of kics-github-action. Hours later, attacker publishes Trivy 0.69.5 and 0.69.6 to Docker Hub - proving they still have access post-disclosure
  • Mar 23, 12:53 UTC: Malicious OpenVSX extensions (ast-results 2.53.0, cx-dev-assist 1.7.0) published. 35 KICS GitHub Action tags hijacked. ast-github-action also compromised
  • Mar 24, ~08:30 UTC: Malicious LiteLLM v1.82.7 published to PyPI
  • Mar 24 (later): LiteLLM v1.82.8 published - more aggressive persistence via Python startup hook
  • Mar 24, ~11:25 UTC: PyPI quarantines both versions. ~3 hours of exposure
  • Mar 27: Telnyx PyPI package compromised with steganographic WAV payload

A 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.

The LiteLLM Payload: Three Stages of Credential Theft

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.

v1.82.7 - Proxy Module Injection

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.

v1.82.8 - Python Startup Hook (Far More Dangerous)

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...")))

What the Malware Steals

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 URLs

Collected 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.

Stage 2: Kubernetes Lateral Movement

If a Kubernetes service account token is detected, the malware:

  1. Enumerates all K8s nodes
  2. Deploys a privileged pod on every node in the kube-system namespace
  3. Pod naming pattern: node-setup-*
  4. Containers: kamikaze (destructive) and provisioner (persistent backdoor)
  5. Installs persistence at /root/.config/sysmon/ on each node

Stage 3: Persistent Backdoor

The 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_state

This runs independently of the original malicious package - removing LiteLLM does NOT remove the backdoor.

How Stream.Security Detects This

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.

Recommendations

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:

  1. Check immediately whether LiteLLM 1.82.7 or 1.82.8 was ever installed in any environment. Check pip install logs, container build histories, and dependency lock files.
  2. Treat as full credential exposure. If either version was installed, assume ALL credentials accessible from that runtime are compromised. This includes cloud provider keys, Kubernetes secrets, SSH keys, database credentials, and CI/CD tokens. Rotating the LiteLLM package alone is not sufficient.
  3. Hunt for persistence. Search for sysmon.service, sysmon.py, /tmp/pglog, /tmp/.pg_state, and node-setup-* pods. The backdoor operates independently of the malicious package.
  4. Block C2 infrastructure at the DNS and network level: models.litellm[.]cloud, checkmarx[.]zone, aquasecurtiy[.]org, and the IP addresses listed above.
  5. Pin and verify dependencies. Use lockfiles with checksums. LiteLLM's CI/CD was compromised because it ran 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.
  6. Monitor for the .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.
  7. Rebuild from known-good images. Do not assume that removing the malicious package remediates the compromise. Follow-on payloads, persistence mechanisms, and stolen credentials may already be in use.
  8. Audit your build pipelines. If any CI/CD step installs tools from external registries without pinning to a specific version and verifying checksums, you have the same class of vulnerability that enabled this campaign.

The Bigger Picture

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.

Appendix: Full Campaign IOCs

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.

Malicious Packages

Network Indicators

# 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 infrastructure

C2 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."

File System Artifacts

# 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

Kubernetes Indicators

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

File Hashes

LiteLLM:

OpenVSX extensions:

About Stream Security

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.

Petr Zuzanov
Principal Security Researcher
We wouldn’t believe it either.