Safeguard
Developer Tools•

Pins or hashes: what keeps Python environments aligned?

Pinned versions keep Python installs consistent across dev, staging, and prod. Hashes add proof the files did not change. Here is when to use each.

Safeguard Security Team
3 min read

To keep Python environments aligned, pin every package to an exact version, including transitive dependencies, and install from that file everywhere. Add hash checking when you want proof that the downloaded files are the ones you reviewed. Pins fix the version; hashes fix the content.

Three levels of repeatable

The pip documentation walks through increasingly strict definitions of repeatable installs. In plain terms:

LevelWhat you doWhat it guards against
Pinned versionsUse the equals operator for each packageSurprise upgrades when a new release appears
Hash checkingAdd a hash for each downloaded fileA changed file under the same version, or a compromised index
WheelhouseBuild all dependencies into one directory or archiveIndex outages and repeated compilation

Each level costs more effort and buys more certainty.

Pins and freeze

Pinning means writing SomePackage == 1.2.3 rather than a loose range. The docs note that pip freeze can generate a requirements file pinning not only top-level packages but all transitive ones. Installing with no-deps adds a safeguard, because pip then installs nothing that is not explicitly listed.

Pinning is easy and works across operating systems and architectures. The docs are candid about its limit: it trusts the place you fetch from, and relies on that place not letting a package change without a version bump. They note PyPI protects against that.

Hashes

Hash checking mode adds a sha256 value per file. The docs say it protects against a compromise of PyPI or the HTTPS certificate chain, and also guards against a package changing without its version changing on indexes that allow it. They call it a good fit for automated server deployments.

The catch is maintenance. Every upgrade means regenerating hashes. Tools such as pip-tools build on pip to generate these files, and the docs point to it for that workflow.

A rule set for three environments

  1. Keep one pinned requirements file per application, generated from your top-level list.
  2. Install from it in dev containers, CI, staging, and production. Do not run an unpinned install anywhere that ships.
  3. Regenerate it on a schedule, in a pull request, so changes are reviewed and tested before they reach prod.
  4. For production deploys, use hash checking if your build process can regenerate hashes cleanly.
  5. Compare the output of pip freeze in each environment against the file. Any difference is drift.

Python version itself can drift, too. See runtime version drift for that.

Try it yourself

Click through it at your own pace, or open it full screen.

Look for how packages and versions are listed per project, the same view you would compare between environments.

What Safeguard does here

Safeguard scans Python dependency files, builds an SBOM, and keeps monitoring it for new advisories, which gives you a record to compare across environments. Try the free scan at gold.safeguard.sh/scan.

Quick check

Does pip freeze capture drift by itself?

It records what is installed in one environment. You still need to run it in each environment and compare the results.

Are hashes required?

No. Pins are the baseline. Hashes are an extra layer for deployments where integrity matters most.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.