Not against tampering. PyPI puts a hash in each download link, and pip checks it to catch corrupted downloads. Because the hash comes from the same remote source as the file, it cannot prove the file is the one you reviewed. Pip's hash-checking mode therefore requires hashes you recorded yourself, in your requirements file.
Two hashes that look the same
Every file on a package index has a hash, and it is easy to assume that makes every install verified. There are actually two different checks.
| Index-supplied hash | Local hash in requirements | |
|---|---|---|
| Where it comes from | The download link fragment, from the index | Your requirements file |
| Who controls it | Whoever controls the index response | You, via version control |
| What it catches | Corrupted or truncated downloads | A file that differs from what you approved |
| Counts toward hash-checking mode | No | Yes |
The pip documentation states this directly. PyPI and certain other servers provide a hash in the fragment of each download URL, and pip checks it as protection against download corruption. Since this hash originates remotely, the guide says, it is not a useful guard against tampering, and it does not satisfy the demand that every package have a local hash.
Why a remote hash cannot be the control
Think about what an attacker needs. If someone could replace a file on an index, they could also update the link that carries its hash. Both come from the same place, so they agree, and the check passes.
A local hash breaks that. You record the digest when you review a release. At install time, pip compares the downloaded file with your recorded digest. A replaced file no longer matches, and the install stops.
The pip guide describes what this protects against: a compromise of the index or of the HTTPS certificate chain, and a package changing without its version number changing on indexes that allow that.
What local hashes do not do
Be honest about the limits.
- They do not tell you a release is safe. If you hash a malicious version on day one, pip will faithfully install it.
- They only cover what is listed. In hash-checking mode, pip requires hashes for all requirements and all dependencies, and requires pins.
- They add upkeep. Every upgrade means new hashes, and a package with wheels for several platforms needs a hash for each file you allow.
So the order of work is: decide the version is acceptable, then record its hash. See how to pin requirements with hashes step by step and generate hashes for a requirements file.
A quick way to tell what you have
Look at your requirements file. If lines carry sha256 values that you or a tool wrote, you have local hashes. If you only see names and versions, you are relying on the index. To bootstrap, run an install with hash enforcement turned on and read the hashes pip reports, then review them before committing.
python -m pip install --require-hashes -r requirements.txt
Safeguard's dependency scanning and SBOM generation record what you actually resolved, which gives reviewers something concrete to compare when a hash changes. A free scan is available at gold.safeguard.sh/scan.
Quick check
Does pip verify the hash in the PyPI link?
Yes, as a corruption check. It does not count as a local hash for hash-checking mode.
Is a hashed requirements file proof the package is safe?
No. It proves the file matches what you recorded. You still need to review the release you recorded.
Sources
Last reviewed: 19 October 2026.