Security News

Cybersecurity news aggregator

HIGH Attacks Snyk

How a Poisoned Security Scanner Became the Key to Backdooring LiteLLM

A supply chain attack compromised the `litellm` Python package on PyPI via versions 1.82.7 and 1.82.8, which contained a three-stage backdoor payload. The attack vector was the theft of the maintainer's PyPI credentials through a prior compromise of the Trivy security scanner used in LiteLLM's CI/CD pipeline. Users must downgrade to version 1.82.6 or earlier and inspect their systems for the specified indicators of compromise.
Read Full Article →

Snyk Blog In this article TL;DR Leading events How it was discovered The attack chain Two Delivery Mechanisms The three-stage payload Stage 1: Information collection Stage 2: Encryption and exfiltration Stage 3: Persistence and lateral movement About TeamPCP Issue suppression Confirmed impact Detection: Are you affected? Remediation Why pip hash verification didn't catch this The broader pattern Indicators of compromise What to do right now How a Poisoned Security Scanner Became the Key to Backdooring LiteLLM Written by Stephen Thoemmes March 24, 2026 0 mins read On March 24, 2026, two versions of the litellm Python package on PyPI were found to contain malicious code. The packages (versions 1.82.7 and 1.82.8) were published by a threat actor known as TeamPCP after they obtained the maintainer's PyPI credentials through a prior compromise of Trivy, an open source security scanner used in LiteLLM's CI/CD pipeline. The malicious versions were available for approximately three hours before PyPI quarantined the package. LiteLLM is downloaded roughly 3.4 million times per day. Snyk has been tracking this incident. If you're a Snyk customer, you may have already seen the in-app banner alert and received an email notification. The vulnerability record is SNYK-PYTHON-LITELLM-15762713 , and status updates are on the Snyk Trust Center . TL;DR Affected package litellm (PyPI) Affected versions 1.82.7, 1.82.8 Safe versions ≤ 1.82.6 Snyk ID SNYK-PYTHON-LITELLM-15762713 First detected 10:39 UTC, March 24, 2026 (1.82.7 upload) PyPI quarantine \~13:38 UTC, March 24, 2026 Attacker TeamPCP (also: PCPcat, Persy_PCP, ShellForce, DeadCatx3) Attack vector Supply chain: compromised PyPI publisher credentials via poisoned Trivy GitHub Action in LiteLLM CI/CD Payload type Three-stage: credential harvester + encrypted exfiltration + persistent backdoor + Kubernetes worm Exfiltration domain models.litellm.cloud (registered March 23, 2026) MITRE ATT\&CK T1546.018 (Python Startup Hooks), T1003 (Credential Dumping), T1610 (Deploy Container) Leading events Time (UTC) Evidence Event Late Feb 2026 GHSA-9p44-j4g5-cfx5 MegaGame10418 Pwn Request against Trivy's CI exploits a pull_request_target workflow to exfiltrate the aqua-bot credentials Mar 19, 17:43 UTC Snyk Trivy coverage Trivy v0.69.4 GitHub Action tags rewritten to point to a malicious release Mar 23, 12:58 UTC Endor Labs (captured pre-deletion PyPI metadata) Checkmarx KICS GitHub Action compromised; checkmarx.zone C2 domain and models.litellm.cloud registered Mar 24, 10:39 UTC Endor Labs (captured pre-deletion PyPI metadata) Malicious litellm 1.82.7 published to PyPI Mar 24, 10:52 UTC FutureSearch ; Endor Labs Malicious litellm 1.82.8 published to PyPI (13 minutes after 1.82.7, with escalated .pth delivery mechanism) Mar 24, 11:48 UTC GitHub issue #24512 FutureSearch (Callum McMahon) opens disclosure issue Mar 24, 12:36 UTC Hacker News HN thread posted; reaches 324 points Mar 24, \~12:44 UTC GitHub issue #24512 (visible in comment timestamps) Bot comments flood issue #24512; issue closed using the compromised maintainer account Mar 24, 13:03 UTC FutureSearch (timestamped update) FutureSearch confirms issue closure and bot spam Mar 24, 13:48 UTC GitHub issue #24518 Clean tracking issue opened Mar 24, 15:09 UTC GitHub comment LiteLLM maintainer confirms all GitHub, Docker, and PyPI keys rotated; maintainer accounts moved to new identities Mar 24, 15:27 UTC GitHub comment Compromised versions deleted; package unquarantined on PyPI How it was discovered Callum McMahon at FutureSearch was testing a Cursor MCP plugin that pulled in litellm as a transitive dependency. Shortly after Python started, his machine became unresponsive due to RAM exhaustion. He traced it to the newly installed litellm package and found litellm_init.pth , a 34,628-byte file in site-packages/ , double base64-encoded. The RAM exhaustion was a side effect of the payload, not an intentional feature. The .pth mechanism fires on every Python interpreter startup. Because the payload spawns a new Python subprocess, and that new process also triggers .pth execution, the result was an unintended fork bomb. McMahon published his findings on futuresearch.ai, and the disclosure spread to r/LocalLLaMA, r/Python, and Hacker News within the hour. The attack chain The attack on LiteLLM started five days earlier with Trivy. March 19: The attackers rewrote Git tags in the trivy-action GitHub Action repository to point to a malicious release ( v0.69.4 ) carrying the same credential-harvesting payload and exfiltration infrastructure used in later operations. (For full details on the Trivy compromise, see Snyk's Trivy GitHub Actions supply chain compromise coverage .) March 23: The same infrastructure was used in a separate attack on Checkmarx KICS (Keep Infrastructure as Code Secure). The C2 domain checkmarx.zone , which impersonates the Checkmarx security company, was registered and activated in this operation. March 24: LiteLLM's CI/CD pipeline ran Trivy as part of its build process, pulling it from apt without a pinned version. The compromised action exfiltrated the PYPI_PUBLISH token from the GitHub Actions runner environment. With that credential, the attackers published litellm 1.82.7 at 10:39 UTC and 1.82.8 at 10:52 UTC, each containing malicious payloads. Initial LiteLLM maintainer response on Hacker News. Two Delivery Mechanisms The two malicious versions used different injection techniques: 1.82.7 (source injection): The payload was base64-encoded and embedded directly inside litellm/proxy/proxy_server.py . It executes when anything imports litellm.proxy , the standard import path for LiteLLM's proxy server mode. 1.82.8 ( .pth file): This version added litellm_init.pth to site-packages/ . The .pth mechanism fires on every Python interpreter startup with no import required, including when pip , python -c , or an IDE's language server starts Python. The .pth file is correctly declared in the wheel's RECORD file, so it passes pip's hash verification. The package passes all standard integrity checks because the malicious content was published using legitimate credentials, not injected after the fact. Snyk Learn has a detailed walkthrough of how this class of attack works in Compromise of a Legitimate Package . CPython maintainers have acknowledged the security risk of .pth startup hooks in issue #113659 , but no patch has been applied. This technique maps to MITRE ATT\&CK T1546.018 (Python Startup Hooks) . The three-stage payload Stage 1: Information collection The script collects: System data: hostname, environment variables, running processes, network routing, recent auth logs from /var/log/auth.log Credentials: SSH private keys, .env files, Git credentials, API keys from shell history, Slack and Discord webhook tokens, CI/CD configs (Jenkins, Travis CI, Terraform), password hashes from /etc/shadow Cloud credentials: AWS credentials and config, GCP and Azure service account files, cloud metadata via direct IMDS queries, AWS Secrets Manager, and SSM Parameter Store (with full IMDSv2 signing implemented in the script) Container and orchestration: Docker config.json (registry credentials), Kubernetes kubeconfig files, service account tokens, secrets, and certificates Cryptocurrency: Bitcoin, Ethereum, Solana, Cardano, Monero wallet files and seed phrases, Ledger hardware wallet configurations Portion of the decoded malicious code, showing structured iteration and collection of secrets in various directories Stage 2: Encryption and exfiltration Collected data is encrypted before transmission: A 32-byte AES-256 session key is generated via openssl rand Data is encrypted with AES-256-CBC (PBKDF2 key derivation) The session key is encrypted with a hardcoded 4096-bit RSA public key (OAEP padding) Everything is bundled into tpcp.tar.gz The bundle is POST'd to https://models.litellm.cloud/ via curl models.litellm.cloud is distinct from the legitimate litellm.ai domain and was registered on March 23 , one day before the malicious packages were published. The RSA public key in this payload is identical to the one found in the Trivy and KICS payloads. Wiz researchers confirmed this match across all three operations, making it the strongest single technical attribution link. Temporary files created during exfiltration: session.key , payload.enc , session.key.enc , tpcp.tar.gz in the system temp directory. Defanged view of the malicious code. The tpcp.tar.gz archive name is a consistent marker across the Trivy, KICS, and LiteLLM operations. Stage 3: Persistence and lateral movement Local persistence: Writes a backdoor Python script to ~/.config/sysmon/sysmon.py (or /root/.config/sysmon/sysmon.py ) Creates a systemd user service at ~/.config/systemd/user/sysmon.service with the description "System Telemetry Service" Registers and enables it: systemctl --user daemon-reload && systemctl --user enable --now sysmon.service The persistence script polls https://checkmarx.zone/raw every 5 minutes for a URL, downloads the file to /tmp/pglog , makes it executable, and runs it as a background process. State is tracked in /tmp/.pg_state . At the time of analysis, the endpoint returns YouTube URLs; the operator can switch to delivering live payloads at will. Kubernetes lateral movement: If the script finds a Kubernetes service account token at the standard mount path, it reads all secrets across every namespace. It then attempts to deploy a privileged pod to every node in kube-system using alpine:latest . These pods mount the host filesystem and install the sysmon backdoor on the underlying node. Malicious pods are named node-setup-{node_name} (node name truncated to 35 characters), with a container named setup . About TeamPCP TeamPCP (also identified as PCPcat, Persy_PCP, ShellForce, and DeadCatx3 per Wiz Threat Center ) has been active since at least December 2025. The actor maintains Telegram channels at @Persy_PCP and @teampcp and embeds the string "Tea

Share this article