Skip to content

Pip hash-checking mode only works when every dependency is covered

Building a complete pip hash-checked install path for Python dependencies

@facilishz.bsky.social
Nov 9, 20264 min read

ython package installation is a code-execution step. A package can run code during a build, and a compromised index or cached artifact can change what a deployment receives. Pip's hash-checking mode provides a concrete defense, but it is all-or-nothing and easy to weaken with one unhashed requirement.

What hash checking verifies

The pip documentation says hash-checking mode uses local hashes embedded in a requirements file to protect against remote tampering and network problems. It can be enforced with --require-hashes, and a requirement with a --hash option activates the mode automatically in current pip versions.

The mode requires hashes for every requirement and every dependency. Requirements must also be pinned, either to a local path, a URL, or an exact version with ==. A hash supplied only in a remote index URL is useful for detecting a corrupted download, but it is not a local policy check because the expected value came from the same remote source.

The strongest practical configuration is:

python -m pip install \
  --require-hashes \
  --only-binary :all: \
  --index-url https://packages.example.invalid/simple \
  -r requirements.txt

The URL is a placeholder. Use the organization's approved index and TLS configuration. --only-binary :all: prevents pip from building a source distribution during this installation, which removes one arbitrary build step from the path. It also means the team must maintain wheels for platforms that need them.

Hash checking does not solve every supply-chain problem

A matching hash proves that the artifact is the one the requirements file names. It does not prove that the package maintainer is trustworthy, that the package has no malicious code, or that the wheel's metadata is correct. The team still needs dependency review, a controlled index, and a process for updating hashes when a version changes.

The mode becomes weak when teams use --no-require-hashes, a local directory without complete hashes, or a script that downloads an artifact and installs it outside the requirements file. It also becomes weak when the hash file is generated from the same compromised artifact immediately before installation. Treat the requirements file as reviewed source, not as a runtime cache.

The pip documentation notes that locally built wheels in hash-checking mode are verified against the original source distribution through cache metadata. That helps, but it does not make source builds safe. Disallow source distributions in the deployment path unless a separate build system reviews and records the inputs.

Build a reproducible requirements set

Resolve dependencies for the target operating system and Python version. Include platform-specific wheels where the application needs them. Store both the exact version and the allowed hashes. Review the diff whenever the set changes, including transitive dependencies that appeared because another package updated.

Generate hashes with the approved pip version, then verify the file in a clean environment:

python -m pip download --only-binary :all: -r requirements.txt -d /tmp/pkgs
python -m pip install --require-hashes --no-index --find-links /tmp/pkgs -r requirements.txt

The offline install is a useful control assertion. If the installation succeeds only when pip can reach the network, the team has not captured all of the required artifacts or has allowed an unlisted dependency. Record the index host, resolver version, and package hashes in the deployment receipt.

For a local project installed after its dependencies, use --no-deps so that pip does not silently resolve a new dependency outside the reviewed set. If the project itself must be built, keep source and build dependencies in a separate, controlled build stage.

Treat hash updates as security changes

Never let a deployment job rewrite hashes automatically and then publish the result. Require a review of the package name, version, source, and diff. Rotate the index credentials separately and do not log authentication tokens. Verify the package index TLS certificate and pin the resolver configuration used by the build.

Hash checking is valuable because it makes tampering visible at the point of installation. Its protection ends wherever an unhashed path begins. The team should be able to show that every artifact in the deployment came from a reviewed requirements file and that no local or network fallback was enabled.

References

Did you enjoy this article?

Recommend it — Standard Reader surfaces well-loved writing to more readers across the network.

Across the AtmosphereDiscussions