How to Vet a Python Package Before Installing It
litellm 1.82.7 and 1.82.8, and Lightning AI 2.6.2 and 2.6.3, hit PyPI before matching source releases appeared on GitHub. A 30-second tag check would have caught either one. Use this five-minute pre-install routine to catch that kind of Python supply chain attack before adding a package to pyproject.toml.
Read the PyPI signals
Open the package page on PyPI and check four things.
-
Download trend. Run
uvxwithpypistatsto see recent download counts:$ uvx pypistats recent requests ┌────────────┬───────────────┬─────────────┐ │ last_day │ last_month │ last_week │ ├────────────┼───────────────┼─────────────┤ │ 52,751,217 │ 1,497,550,436 │ 343,955,728 │ └────────────┴───────────────┴─────────────┘Stars and download totals can be inflated by bot traffic and CI runs, so weight them lightly.
-
Release cadence. The PyPI sidebar shows release history. Look for steady releases over a year or more, not a single recent burst followed by silence (or vice versa).
-
Maintainer activity. The PyPI page links to the maintainer’s other projects. A single-package account with a recent first release deserves more scrutiny than a maintainer with several older projects.
-
Last-release date. A package whose last release was years ago may still be fine if the API surface is stable, but combine that with no recent commits on the source repo and you’re depending on unmaintained code.
Verify PyPI matches the source repo
A PyPI release with no matching tag in the source repo is the signature of a real attack pattern, not a theoretical one. Of every check in this guide, this is the one to do first.
Click “Project links” on the PyPI page and confirm the “Source” link points at a live repository. Compare the latest PyPI version to the latest tag or release on the source repo. If PyPI lists a version that has no matching tag in Git, treat the release as compromised until proven otherwise.
A PEP 740 attestation from Trusted Publishing is the cryptographic version of this check, tying the artifact on PyPI to the workflow run that built it. Coverage is still partial, so a missing attestation isn’t evidence of compromise, but a present and valid one is strong evidence of a clean release path.
Skim the recent commits
Don’t try to read the whole source. Read what changed since the last version you trusted, or in the past two weeks if the package is new to you:
git log --since="2 weeks ago" --statPyPI’s release page also shows file changes between versions if the package uploads sdists.
What to scan for, focused on why installing a Python package can run code:
- Install-time hooks: a
setup.pywith a customcmdclass, new.pthfiles, or import-time network calls in__init__.py. - Suspicious URLs or base64 blobs that decode to executable code.
- New
subprocess,os.system,eval, orexeccalls in package init. - Obfuscated strings or fetch-and-execute patterns near install time.
Many malicious PyPI uploads use one of these patterns to run code at install or first import. Spotting any of them in a diff is reason enough to back out and ask questions.
Check reputation tools
A handful of services aggregate signals across PyPI, GitHub, and vulnerability databases. They miss things, but they take seconds to consult.
- Socket scans dependencies for malicious behavior and surfaces 70+ signals across supply chain risk, vulnerabilities, quality, maintenance, and licensing. PyPI packages have a page at
https://socket.dev/pypi/package/<name>. - Snyk Advisor computes a package health score across popularity, maintenance, security, and community, and labels packages “healthy,” “sustainable,” or “risky.” Each Python package has a page at
https://snyk.io/advisor/python/<name>. - deps.dev is Google’s package insights service across major language ecosystems. It surfaces the dependency graph, OSV advisories, and the OpenSSF Scorecard for the linked source repo. PyPI packages have a page at
https://deps.dev/pypi/<name>.
Treat each as a smoke detector, not a verdict. A clean Snyk score on a package whose PyPI release doesn’t match any GitHub tag is still suspicious.
Know what this checklist won’t catch
A small, careful malicious diff in a high-traffic package will pass every step here. The maintainer’s account gets compromised, the attacker pushes a tag and a release together, the diff is small enough that nobody notices for hours, and Socket and Snyk haven’t scored it yet. That is the litellm and Lightning AI playbook, and the manual checklist isn’t enough on its own.
That’s why the project-side defenses are the backstop:
- Set a dependency cooldown with
--exclude-newerso brand-new versions don’t reach your environment. - Pin exact versions with a uv lockfile so a tampered upload doesn’t silently replace what you resolved.
- Add hash pinning so a swapped artifact fails verification at install time.
- Run vulnerability scans in CI to catch CVEs after they’re disclosed.
Learn more
- What is a Python supply chain attack?
- How to protect against Python supply chain attacks with uv
- How to use a uv lockfile for reproducible Python environments
- How to pin dependencies with hashes in uv
- How to scan Python dependencies for vulnerabilities
- Why installing a Python package can run code
- Bernát Gábor, Defense in Depth: A Practical Guide to Python Supply Chain Security