Commissioned, Curated and Published by Russ. Researched and written with AI.


What’s New This Week

The TeamPCP supply chain campaign – responsible for the Trivy GitHub Actions and Docker Hub compromises earlier in March 2026 – has pivoted to PyPI. LiteLLM versions 1.82.7 and 1.82.8 contain a malicious .pth file that abuses a Python interpreter mechanism to execute a credential stealer on every Python startup, before any user code runs. The payload collects SSH keys, cloud credentials, Kubernetes tokens, database passwords, shell history, and crypto wallets, then exfiltrates everything encrypted to an attacker-controlled domain designed to look like LiteLLM’s official infrastructure. If you have either version installed anywhere – including CI/CD runners – assume full credential compromise and rotate everything.


Changelog

DateSummary
24 Mar 2026Initial writeup covering the .pth mechanism, full payload analysis, and remediation steps.

The incident

On March 24, 2026, LiteLLM versions 1.82.7 and 1.82.8 on PyPI were found to contain a malicious .pth file: litellm_init.pth, 34,628 bytes. The file is listed openly in the wheel’s own RECORD manifest:

litellm_init.pth,sha256=ceNa7wMJnNHy1kRnNCcwJaFjWX3pORLfMh7xGL8TUjg,34628

GitGuardian confirmed this is the same TeamPCP infostealer used in the Trivy campaign. The Trivy timeline: a CI compromise in late February 2026 stole a Personal Access Token. Partial remediation on March 1 left credentials insufficiently rotated. On March 19, the attacker reused still-valid credentials to weaponise 75 of 77 trivy-action tags. On March 24, the campaign pivoted to PyPI.

This is the same payload, the same encryption scheme, the same exfiltration infrastructure – deployed via a completely different ecosystem.


Why .pth files are an ideal delivery mechanism

Python’s .pth mechanism is a site-packages feature most developers never think about. Any file with a .pth extension placed in site-packages/ is processed automatically by the Python interpreter at startup. No import statement. No entry point. No __init__.py. The interpreter just runs it – every time, for every Python process on the system.

This means the attack does not require import litellm anywhere in your codebase. It does not require your code to do anything at all. It executes before your application code starts. It executes in CI/CD runners the moment Python initialises. It executes in containers, virtualenvs, any environment where the malicious LiteLLM wheel was installed.

The .pth file survives pip uninstall of packages that don’t clean up properly. It survives virtual environment recreation if the base installation is affected. It is persistent by design, because the .pth mechanism was designed for legitimate path manipulation – a perfectly reasonable feature that becomes a clean rootkit delivery path.


What the payload does

The payload in litellm_init.pth is double base64-encoded. When decoded, it executes a two-stage infostealer.

Stage 1 – Collection

The malware builds a complete picture of the compromised environment:

System identity

  • Hostname, full uname -a output, network interface configuration (ip addr), routing table (ip route)
  • All environment variables via printenv

Credentials and keys

  • SSH keys: ~/.ssh/id_rsa, id_ed25519, id_ecdsa, authorized_keys, known_hosts, config
  • Git credentials: ~/.gitconfig, ~/.git-credentials
  • AWS: ~/.aws/credentials, ~/.aws/config, IMDS token and security credentials from the instance metadata service
  • Kubernetes: ~/.kube/config, /etc/kubernetes/*.conf, service account tokens
  • GCP: ~/.config/gcloud/application_default_credentials.json
  • Azure: the full ~/.azure/ directory
  • Docker: ~/.docker/config.json and /kaniko/.docker/config.json (explicitly targeting Kaniko CI builds)
  • Package managers: ~/.npmrc, ~/.vault-token, ~/.netrc

History and context

  • Shell history: ~/.bash_history, ~/.zsh_history

Crypto wallets

  • ~/.bitcoin/, ~/.ethereum/keystore/, ~/.config/solana/ and other wallet paths

Infrastructure secrets

  • SSL/TLS private keys: /etc/ssl/private/, Let’s Encrypt .pem and .key files
  • CI/CD configuration: terraform.tfvars, .gitlab-ci.yml, .travis.yml, Jenkinsfile, .drone.yml
  • Database credentials: PostgreSQL, MySQL, Redis, and LDAP configuration files

This is not a targeted collection. This is a sweep of every credential path that matters in a modern engineering environment. A single affected developer machine hands over everything needed to impersonate that engineer across every system they touch.

Stage 2 – Encryption and exfiltration

The payload does not transmit in the clear:

  1. Generates a random 32-byte AES-256 session key via openssl rand
  2. Encrypts the collected data with AES-256-CBC and PBKDF2 key derivation
  3. Encrypts that session key with a hardcoded 4096-bit RSA public key using OAEP padding
  4. Packs everything into tpcp.tar.gz
  5. Exfiltrates via curl POST to https://models.litellm.cloud/

That last point deserves attention. The exfiltration domain is litellm.cloud – not litellm.ai, which is LiteLLM’s official domain. The subdomain models.litellm.cloud is designed to look like a legitimate LiteLLM API endpoint. Network monitoring tools that look for obvious red flags will see HTTPS traffic to what appears to be the vendor’s own infrastructure.

The RSA encryption means the stolen data cannot be decrypted without the attacker’s private key. There is no way to recover or examine what was taken by analysing network traffic.


Scope and exposure

LiteLLM is a widely used Python library for normalising calls across different LLM providers – OpenAI, Anthropic, Azure, Bedrock, Vertex AI, and others. It is common in AI application development, internal tooling, and CI/CD workflows that interact with language model APIs. The library has broad adoption precisely in the kinds of environments that hold high-value cloud credentials.

Affected versions: 1.82.7 and 1.82.8 on PyPI.

Any environment that installed either version – developer workstation, CI/CD runner, container, serverless function – should be treated as compromised. The .pth mechanism runs before user code, so the exposure begins at install time, not at first use.


What to do

Step 1: Check your version

pip show litellm

If the output shows Version: 1.82.7 or Version: 1.82.8, you are affected.

Step 2: Assume full credential compromise

If either version is installed, do not wait for forensic confirmation. The payload runs silently at Python startup. There is no log entry, no error, no indication it ran. Treat the machine as fully compromised and rotate everything:

  • SSH keys (all of them in ~/.ssh/)
  • AWS, GCP, and Azure access keys and service account credentials
  • Kubernetes service account tokens
  • API keys for any service
  • Database passwords
  • CI/CD secrets
  • npm publish tokens
  • Any credential stored in the paths listed above

Step 3: Remove the affected version

pip uninstall litellm

Then upgrade to a clean version:

pip install litellm --upgrade

Verify the installed version is not 1.82.7 or 1.82.8.

Step 4: Treat any affected CI/CD runner as fully compromised

If a runner installed either version during a pipeline execution, that runner had its Python startup context hijacked. Treat it the same as a developer machine. Rotate all secrets the runner had access to.

Step 5: Audit .pth files in site-packages

find $(python -c "import site; print(' '.join(site.getsitepackages()))") -name "*.pth" 2>/dev/null

Inspect any .pth file you do not recognise. Legitimate .pth files are short path entries, not kilobytes of encoded content.

Step 6: Pin future installs to hashes

pip install litellm==X.X.X --require-hashes

Hash pinning ensures that what you install matches what you audited. It does not help after a compromise, but it prevents this class of attack going forward. Use pip-compile with --generate-hashes to maintain a verified lockfile.


The pattern

The Trivy compromise (GitHub Actions, Docker Hub, npm), and now LiteLLM (PyPI). Same infostealer. Same encryption scheme. Same exfiltration infrastructure design. Different ecosystems, different attack surfaces, same team.

What links these targets is the same logic as the Trivy attack: compromise tools that sit in privileged positions within the development and deployment pipeline. Security teams adopted Trivy because it was a trusted scanner. AI teams adopted LiteLLM because it is a trusted LLM abstraction layer. Both are deeply integrated into the workflows they serve. Both run with access to the credentials those workflows use.

The payload’s collection list is telling. It is not a speculative sweep – it is a precise map of everything a Python developer working with cloud infrastructure, Kubernetes, and AI APIs is likely to have on their machine or in their CI/CD environment. Someone put significant time into building this target list.

The .pth delivery mechanism is particularly well-chosen for this attack. It bypasses import-time scanning, executes before application code, and is obscure enough that most developers have never audited their site-packages for .pth files they did not install intentionally. The mechanism itself is legitimate and necessary; it is not a vulnerability in Python. It is just a feature that can be abused.

If you are running LiteLLM, check your version now.


Sources