# How to Vet a Python Package Before Installing It


[litellm 1.82.7 and 1.82.8](/blog/litellm-supply-chain-attack-and-securing-python-dependencies/), and [Lightning AI 2.6.2 and 2.6.3](/blog/lightning-pypi-compromise-import-time-supply-chain-attack/), 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](/handbook/explanation/what-is-a-python-supply-chain-attack/) before adding a package to `pyproject.toml`.

## Read the PyPI signals

Open the package page on [PyPI](https://pydevtools.com/handbook/explanation/what-is-pypi.md) and check four things.

- **Download trend.** Run [`uvx`](https://pydevtools.com/handbook/reference/uvx.md) with [`pypistats`](https://github.com/hugovk/pypistats) to see recent download counts:

    ```console
    $ 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](/handbook/explanation/why-pylock-toml-includes-digital-attestations/) from [Trusted Publishing](https://pydevtools.com/handbook/explanation/why-use-trusted-publishing-for-pypi.md) 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:

```bash
git log --since="2 weeks ago" --stat
```

PyPI'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](/handbook/explanation/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](https://socket.dev/) 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](https://snyk.io/advisor/python) 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](https://deps.dev/) is Google's package insights service across major language ecosystems. It surfaces the dependency graph, OSV advisories, and the [OpenSSF Scorecard](https://scorecard.dev/) 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-newer`](https://pydevtools.com/handbook/how-to/how-to-protect-against-python-supply-chain-attacks-with-uv.md) so brand-new versions don't reach your environment.
- Pin exact versions with a [uv lockfile](https://pydevtools.com/handbook/how-to/how-to-use-a-uv-lockfile-for-reproducible-python-environments.md) so a tampered upload doesn't silently replace what you resolved.
- Add [hash pinning](https://pydevtools.com/handbook/how-to/how-to-pin-dependencies-with-hashes-in-uv.md) so a swapped artifact fails verification at install time.
- Run [vulnerability scans](https://pydevtools.com/handbook/how-to/how-to-scan-python-dependencies-for-vulnerabilities.md) in CI to catch CVEs after they're disclosed.

## Learn more

- [What is a Python supply chain attack?](/handbook/explanation/what-is-a-python-supply-chain-attack/)
- [How to protect against Python supply chain attacks with uv](https://pydevtools.com/handbook/how-to/how-to-protect-against-python-supply-chain-attacks-with-uv.md)
- [How to use a uv lockfile for reproducible Python environments](https://pydevtools.com/handbook/how-to/how-to-use-a-uv-lockfile-for-reproducible-python-environments.md)
- [How to pin dependencies with hashes in uv](https://pydevtools.com/handbook/how-to/how-to-pin-dependencies-with-hashes-in-uv.md)
- [How to scan Python dependencies for vulnerabilities](https://pydevtools.com/handbook/how-to/how-to-scan-python-dependencies-for-vulnerabilities.md)
- [Why installing a Python package can run code](https://pydevtools.com/handbook/explanation/why-installing-a-python-package-can-run-code.md)
- Bernát Gábor, [Defense in Depth: A Practical Guide to Python Supply Chain Security](https://bernat.tech/posts/securing-python-supply-chain/)
