97Mmonthly downloads
3attack stages
10new detection rules
<24hresponse time

On March 24, 2026, litellm versions 1.82.7 and 1.82.8 were published to PyPI with malicious code. litellm has 97 million monthly downloads and is a transitive dependency of dspy and dozens of other AI agent frameworks. The maintainer account was likely fully compromised. No corresponding GitHub tag or release exists.

The attack was found by accident. An MCP plugin inside Cursor pulled litellm as a transitive dependency. A fork bomb bug in the malware consumed all RAM and crashed the machine. Without that bug, it could have gone undetected for weeks. litellm #24512

The attack

The attack vector is a .pth file (litellm_init.pth). Python runs .pth files on every interpreter startup when the package is installed in the environment. Not on import. Not via setup.py. Just having the package installed is enough. Every Python process on the machine executes the payload.

The malware runs in three stages:

1. Collection
Harvests SSH keys, cloud credentials, K8s configs, API keys, shell history, database passwords, crypto wallets from the host.
2. Exfiltration
Encrypts with hardcoded 4096-bit RSA + AES-256-CBC, bundles into tar, POSTs to attacker-controlled domain mimicking litellm infra.
3. Persistence
If the service account has cluster-admin privileges, creates privileged K8s pods on every node. Installs systemd backdoor. Survives reboots.

Collection

A Python script harvests sensitive files from the host:

It also runs commands to dump os.environ and queries cloud metadata endpoints (169.254.169.254, metadata.google.internal) to steal container and VM credentials.

Exfiltration

The collected data is encrypted with a hardcoded 4096-bit RSA public key using AES-256-CBC (random session key encrypted with the RSA key), bundled into a tar archive, and POSTed to https://models.litellm.cloud/. The domain mimics legitimate litellm infrastructure but is attacker-controlled.

Lateral movement and persistence

If a Kubernetes service account token is present and has sufficient privileges (cluster-admin or equivalent), the malware reads all cluster secrets across all namespaces and creates privileged alpine:latest pods on every node in kube-system. Each pod mounts the host filesystem and installs a persistent backdoor at /root/.config/sysmon/sysmon.py with a systemd user service. Clusters with least-privilege RBAC limit the blast radius.

On the local machine, the same persistence mechanism is installed at ~/.config/sysmon/sysmon.py with ~/.config/systemd/user/sysmon.service.

Why this matters for MCP

Most MCP servers are Python packages. MCP clients like Cursor, Claude Desktop, and VS Code launch them with package executors like uvx and npx that auto-download dependencies on every run. No lockfile. No hash verification. Whatever PyPI or npm serves is what executes on your machine.

This is exactly how the attack spread. Callum McMahon (who discovered the attack) was running an MCP server in Cursor with an unpinned litellm dependency. When Cursor auto-loaded the server, uvx pulled litellm 1.82.8 from PyPI. The malware was live on PyPI for less than an hour. McMahon's machine was compromised within minutes.

No prompt injection needed. No LLM trickery. Just a poisoned package auto-downloaded by a package executor that MCP clients use by default.

litellm was also a dependency of dspy (among others). A simple pip install dspy would pull in the poisoned version. Any MCP server that depends on litellm, directly or transitively, is a vector.

The MCP protocol has no built-in mechanism for scanning dependencies before running a server, pinning versions in MCP configs, restricting a server's network access, detecting if dependencies changed between runs, or auditing what files a server process reads.

Check if you are affected

Quick check with Aguara

brew install garagon/tap/aguara
aguara check

Or without Homebrew: curl -fsSL https://raw.githubusercontent.com/garagon/aguara/main/install.sh | bash

aguara check scans your Python environments for:

If it finds something:

aguara clean

aguara clean shows what it found, asks for confirmation, then removes the compromised package, quarantines malicious files to /tmp/aguara-quarantine/ for forensic analysis, and purges package caches.

Manual check (no install required)

If you prefer not to install a tool:

# Check if you have the compromised version
pip show litellm | grep Version

# Check for the malicious .pth file
find $(python -c "import site; print(site.getsitepackages()[0])") -name "litellm_init.pth"

# Check for persistence backdoors
ls ~/.config/sysmon/sysmon.py
ls ~/.config/systemd/user/sysmon.service

# If running Kubernetes
kubectl get pods -n kube-system | grep node-setup

If any of these return results: uninstall litellm, delete the .pth file, remove ~/.config/sysmon/, and rotate every credential on that machine. SSH keys, cloud provider tokens, API keys in .env files, database passwords, Kubernetes configs. Assume they were exfiltrated.

What we shipped

Aguara: 10 detection rules for supply chain exfiltration

New rule category (supply-chain-exfil) that detects the code patterns from this attack when scanning MCP server directories:

aguara scan /path/to/mcp-server/ --severity high
RuleWhat it detects
SC-EX-001Python code reading credential files (~/.ssh/, ~/.aws/, etc.) via open() or pathlib
SC-EX-002File contents being base64/AES encoded before transmission
SC-EX-003Bulk os.environ access combined with HTTP POST in the same file
SC-EX-004.pth files with executable content (import, subprocess, exec)
SC-EX-005Cloud metadata endpoint access (169.254.169.254) in Python source
SC-EX-006Kubernetes secrets API access or privileged pod creation
SC-EX-007Systemd service or crontab persistence installation
SC-EX-008Hardcoded RSA/AES key material in source code
SC-EX-009Tar/zip archive creation combined with HTTP POST
SC-EX-010.pth file presence (flags for review)

SC-EX-004 would have caught the litellm .pth file. SC-EX-001 combined with SC-EX-009 would have caught the credential harvesting and tar+POST exfiltration pattern. These are static analysis rules. No LLM needed. Open source.

Rules that use match_mode: all require multiple patterns to match in the same file. SC-EX-003 only fires when bulk os.environ access AND an HTTP POST appear in the same file, not when a program reads a single environment variable.

Aguara check and clean

aguara check scans Python environments and package caches (uv, pip, npx) for compromised packages, malicious .pth files, and persistence backdoors. aguara clean shows what it found, asks for confirmation, then quarantines and removes everything. Two commands, zero flags.

oktsec: egress sandboxing for MCP servers

oktsec is a security proxy for MCP and agent-to-agent communication. The new egress_sandbox config forces MCP server subprocesses to route all HTTP traffic through oktsec's forward proxy:

# oktsec.yaml
gateway:
  servers:
    my-server:
      transport: stdio
      command: python
      args: [server.py]
      egress_sandbox: true

When enabled, oktsec injects HTTP_PROXY and HTTPS_PROXY environment variables into the subprocess. Egress policies then apply. Only explicitly allowed domains are reachable. The litellm exfiltration POST to models.litellm.cloud fails because the domain is not in the allowed list.

Limitation: this catches HTTP/HTTPS traffic. Raw TCP sockets that don't respect proxy environment variables bypass it. For stronger isolation, run the MCP server in a Docker container with network restricted to the proxy.

Dependency rug-pull detection

oktsec's gateway now hashes dependency manifests (requirements.txt, package-lock.json, go.sum) on startup and compares to stored hashes from the previous run:

WARNING: research-server dependency manifest changed
  requirements.txt: sha256 changed (was a3f2c8... now d9e1b7...)
  Run 'oktsec audit deps /opt/servers/research/' to verify

This catches the litellm scenario: a version bump from 1.82.7 to 1.82.8 changes the manifest hash. The warning fires before the server starts.

oktsec audit deps

Scans dependency manifests against the OSV.dev vulnerability database:

oktsec audit deps /path/to/mcp-server/

Checks each package+version for known vulnerabilities, flags high dependency counts, warns on missing lockfiles and unpinned versions. Supports --json for CI and --strict to fail on warnings.

What this does not cover

The litellm malware executes at Python interpreter startup via a .pth file. It reads files and makes network requests using Python stdlib, not through MCP tool calls. This means:

Protecting your MCP servers

Scan before running.

aguara scan /path/to/server/ --severity high

Audit dependencies.

oktsec audit deps /path/to/server/

Pin dependencies. If your MCP config uses uvx, pin the version: uvx run package@1.2.3, not uvx run package. Same for npx. For pip, use exact version pins (==1.82.7) with lockfiles and hashes (pip install --require-hashes). Unpinned dependencies pull whatever the registry serves at that moment.

Restrict network access. Enable egress_sandbox: true or run MCP servers in Docker containers with restricted networking. A server that can only reach the APIs it needs cannot exfiltrate to arbitrary domains.

Monitor dependency changes. Enable dep_check: true in oktsec gateway config. You will get a warning when any manifest changes between runs.

Minimize dependencies. Every transitive dependency is attack surface. An MCP server with 5 dependencies is safer than one with 300. litellm has 300+.

Links

Get started

One binary. 255 detection rules. Delegation chains. Deploy in minutes.

Stay informed

New releases, security research, and detection rule updates. No spam.

Be the first to know about new releases and research.