How to Check If Your Python Package Is Compromised on PyPI
My machine was stuttering hard. htop was taking 10+ seconds to load, CPU pegged at 100% with no obvious culprit. I thought it was a runaway process or maybe malware from a sketchy download.
Turns out, I had installed a compromised Python package from PyPI.
The LiteLLM versions 1.82.7 and 1.82.8 contained malicious code that executed on every Python process startup. Here’s how I discovered it, and more importantly, how you can check your own environment.
The Discovery
I had installed litellm the day before for a project:
pip install litellmThe next morning, my machine was crawling. I opened htop and noticed Python processes consuming excessive CPU. Strange, since I wasn’t running anything intensive.
I killed the processes, but they reappeared. That’s when I knew something was wrong.
Step 1: Check for Suspicious .pth Files
The malicious payload was hidden in a .pth file. These files execute automatically when Python starts, making them perfect for persistence.
# Find all .pth files in your Python environmentfind $(python3 -c "import sysconfig; print(sysconfig.get_paths()['purelib'])") \ -name "*.pth" -exec grep -l "subprocess\|exec\|eval\|base64" {} \;I ran this and found litellm_init.pth containing obfuscated code. A legitimate .pth file should only contain import paths, not executable code.
/usr/local/lib/python3.11/site-packages/litellm_init.pthStep 2: Verify Version Against Official Sources
The compromised versions (1.82.7 and 1.82.8) had no corresponding GitHub release. This was a major red flag.
pip show litellm | grep VersionVersion: 1.82.8Then I checked the official repository:
curl -s https://api.github.com/repos/BerriAI/litellm/releases/latest | jq -r '.tag_name'v1.82.6Version 1.82.8 didn’t exist on GitHub. It was uploaded directly to PyPI, bypassing the normal release process.
Step 3: Compare Package Hashes
Every package on PyPI has published SHA256 hashes. If your installed package has a different hash, it’s been tampered with.
pip download litellm==1.82.8 --no-deps -d /tmpsha256sum /tmp/litellm-1.82.8-*.whlcurl -s https://pypi.org/pypi/litellm/1.82.8/json | jq '.urls[0].digests.sha256'If these don’t match exactly, you have a compromised package.
Step 4: Check for Persistence Mechanisms
The LiteLLM attack created persistence through systemd user services:
ls -la ~/.config/sysmon/ 2>/dev/nullls -la ~/.config/systemd/user/sysmon.service 2>/dev/null/home/user/.config/sysmon/sysmon.py/home/user/.config/systemd/user/sysmon.serviceThese files shouldn’t exist. The attack created a systemd service that runs on login, ensuring the malware persists even after you remove the package.
Step 5: Check Cache Directories
Package managers like uv and pip cache downloads. A malicious package can hide in these caches:
find ~/.cache/uv -name "litellm_init.pth" 2>/dev/nullpip cache infofind $(pip cache dir) -name "*litellm*" 2>/dev/nullI found the malicious .pth file in my uv cache, which would have reinfected me if I had run uv pip install again.
What the Attack Was Doing
After investigation, I discovered what the malware was harvesting:
+---------------------------+----------------------------------+| Target | Location |+---------------------------+----------------------------------+| SSH private keys | ~/.ssh/id_rsa, ~/.ssh/config || AWS credentials | ~/.aws/credentials || GCP credentials | ~/.config/gcloud/ || Azure credentials | ~/.azure/ || Kubernetes configs | ~/.kube/config || Environment variables | All env vars with secrets || Crypto wallets | Various wallet file extensions |+---------------------------+----------------------------------+This is a classic credential harvesting attack disguised as a legitimate library update.
Automated Security Audit Tools
After this experience, I set up automated checks:
pip install pip-auditpip-audit --desc --aliasespip install safetysafety check# Scan for known vulnerabilitiespip-audit
# Check requirements.txt before installingpip-audit -r requirements.txtHow to Protect Yourself
1. Hash Pinning in requirements.txt
Instead of flexible version specs, pin exact hashes:
litellm==1.82.6 \ --hash=sha256:abc123... \ --hash=sha256:def456...This ensures you get the exact package you expect, not a tampered version.
2. Verify Before Installing
# Download without installingpip download package_name==version --no-deps -d /tmp
# Check the hashsha256sum /tmp/package_name*.whl
# Compare with PyPIcurl -s https://pypi.org/pypi/package_name/version/json | jq '.urls[0].digests.sha256'
# Only install if hashes matchpip install /tmp/package_name*.whl3. Check for GitHub Releases
Legitimate packages usually have GitHub releases that match PyPI versions:
# Get PyPI versionpypi_version=$(pip index versions package_name 2>/dev/null | head -1 | awk '{print $2}')
# Get latest GitHub releasegh_version=$(curl -s https://api.github.com/repos/owner/repo/releases/latest | jq -r '.tag_name')
echo "PyPI: $pypi_version, GitHub: $gh_version"If PyPI has versions that don’t exist on GitHub, be suspicious.
4. Monitor Your Environment
Create a simple check script:
#!/bin/bash
echo "=== Checking for suspicious .pth files ==="find $(python3 -c "import sysconfig; print(sysconfig.get_paths()['purelib'])") \ -name "*.pth" -exec grep -l "subprocess\|exec\|eval\|base64\|compile" {} \; 2>/dev/null
echo ""echo "=== Checking for persistence mechanisms ==="ls -la ~/.config/sysmon/ 2>/dev/null && echo "WARNING: sysmon directory found"ls -la ~/.config/systemd/user/*.service 2>/dev/null | grep -v "ExpectedService"
echo ""echo "=== Running pip-audit ==="pip-audit --desc 2>/dev/null || echo "pip-audit not installed"Run this periodically or add it to your CI/CD pipeline.
Cleanup Steps
If you find you’ve installed a compromised package:
# 1. Remove the packagepip uninstall litellm -y
# 2. Remove persistence mechanismsrm -rf ~/.config/sysmon/rm -f ~/.config/systemd/user/sysmon.servicesystemctl --user daemon-reload
# 3. Clear cachespip cache purgerm -rf ~/.cache/uv/
# 4. Check for .pth remnantsfind $(python3 -c "import sysconfig; print(sysconfig.get_paths()['purelib'])") \ -name "litellm_init.pth" -delete
# 5. Rotate all potentially compromised credentials# - SSH keys# - Cloud provider credentials# - API keys stored in env vars# - Database passwordsWhy This Keeps Happening
PyPI’s model trusts package maintainers. If a maintainer’s credentials are compromised (as happened with LiteLLM), malicious versions can be uploaded directly.
+----------------+------------------------+----------------------------+| Stage | What Happens | Detection Difficulty |+----------------+------------------------+----------------------------+| Credential | Attacker gains access | Hard - happens upstream || theft | to maintainer account | |+----------------+------------------------+----------------------------+| Malicious | Attacker uploads | Medium - no GitHub release || upload | compromised version | for the version |+----------------+------------------------+----------------------------+| User install | pip install grabs | Easy - but only after || | compromised package | fact |+----------------+------------------------+----------------------------+| Code execution | .pth file runs on | Trivial - but damage done || | every Python start | |+----------------+------------------------+----------------------------+The supply chain attack surface is enormous, and most developers don’t verify what they install.
The Reality Check
I consider myself security-conscious. I use a password manager, I’m careful about downloads, I keep my system updated. But one pip install nearly cost me my SSH keys, cloud credentials, and who knows what else.
The steps I’ve outlined above are now part of my workflow. Not because I’m paranoid, but because I learned the hard way that the package you install might not be the package you think it is.
Quick checklist for every pip install:
- Is there a matching GitHub release for this version?
- Does the hash match PyPI’s published hash?
- Are there any suspicious .pth files after installation?
- Does pip-audit report any vulnerabilities?
If any of these fail, stop and investigate before proceeding.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Reddit - LiteLLM Compromise Discussion
- 👨💻 PyPI Security Best Practices
- 👨💻 pip-audit Documentation
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments