Skip to content

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 uvx with pypistats to 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. pypistats.org rate-limits requests by IP address and refreshes its data once a day, so a request that fails usually succeeds after a short wait; don’t retry it in a loop.

  • 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. Clone it and list the newest tags:

git clone https://github.com/psf/requests
cd requests
git tag --sort=-creatordate | head
v2.34.2
v2.34.1
v2.34.0
v2.34.0.dev1
v2.33.1
v2.33.0
v2.32.5
v2.32.4
v2.32.3
v2.32.2

Compare the newest tag to the latest version on PyPI. Here PyPI lists requests 2.34.2 and the repo has v2.34.2, so the release checks out. If PyPI lists a version that has no matching tag in Git, treat the release as compromised until proven otherwise.

A PEP 740 attestation (the standard for cryptographically tying a PyPI file to the workflow that built it) from Trusted Publishing is the cryptographic version of this check. 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. In the clone from the previous step, 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" --stat
commit dae7ef63b4df6eded86637f251fc4e3a06c3b479
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Wed Sep 2 12:15:52 2026 -0600

    Bump https://github.com/astral-sh/ruff-pre-commit (#7616)
    ...

 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

What to scan for, focused on why installing a Python package can run code:

  • Install-time hooks: a setup.py with a custom cmdclass, new .pth files, or import-time network calls in __init__.py.
  • Suspicious URLs or base64 blobs that decode to executable code.
  • New subprocess, os.system, eval, or exec calls 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 reports signals across supply chain risk, vulnerabilities, quality, maintenance, and licensing. PyPI packages have a page at https://socket.dev/pypi/package/<name>.
  • Snyk scores each package’s health out of 100 across security, popularity, maintenance, and community, with labels such as “Sustainable” for maintenance and “Key ecosystem project” for popularity. Each Python package has a page at https://security.snyk.io/package/pip/<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:

Last updated on