Pin each Python package to an exact version with a hash, turn on pip's hash checking mode, and pin each model to a full commit hash. Then a rebuild installs the same bytes you tested. This covers the code side and the model side of an ML project together.
Two halves of the same problem
An ML service pulls two kinds of outside material: libraries from a package index and models from a hub. Both can change under a loose reference. A library range can pick up a new release. A model reference to a branch can move to new weights.
Locking both gives you a build that can be repeated and audited.
Pip hash checking mode
The pip documentation describes hash checking mode as using local hashes, embedded in a requirements file, to protect against remote tampering and network issues. Adding a hash option to any requirement turns the mode on for the whole install. You can also force it with the require hashes flag.
Two rules from the docs matter:
- Every requirement must be pinned, to an exact version with double equals, a URL or a file path.
- Hashes are required for every requirement and every dependency. If a dependency is not listed and hashed, the install fails.
That second rule means you must include transitive dependencies. Generate the file with a tool rather than by hand.
Steps
- Create a requirements input file listing your direct dependencies.
- Use a lock tool that resolves dependencies and writes hashes (check your tool's documentation for the option), and commit the output.
- Install with the flag that requires hashes.
pip install --require-hashes -r requirements.txt
- Rebuild the lock file on a schedule and review the diff in a pull request.
- For the model, pin a full commit hash in code or config, not main. The hub download guide accepts a branch, tag, pull request reference or commit hash through the revision option, and notes the commit hash must be the full length hash.
- Record the model's file digests alongside the lock file.
What to watch
| Risk | Control |
|---|---|
| New library release changes behavior | Exact version pin |
| Tampered or swapped package file | Hash checking mode |
| Moving model branch | Full commit hash |
| Model repo gains new files | Pinned revision plus a file allow list |
| Packages that download models at run time | Check their docs and set an explicit revision |
Be aware of one more gap. Some libraries fetch extra files or models at run time. A locked requirements file does not capture those downloads, so find them and pin them in code or configuration.
Updating without drift
Pinning does not mean never updating. Schedule it. Rebuild the lock, run your tests and an evaluation set, and review what changed. Treat a model revision bump like a dependency upgrade, since weights can change behavior.
Try it yourself
Click through it at your own pace, or open it full screen.
Open the inventory and look at how each package shows an exact version. That is the information your lock file should preserve.
Quick check
Do I have to hash transitive dependencies too?
Yes. The pip docs say hashes are required for every requirement and every dependency in hash checking mode, or the install fails.
Is a version pin enough without hashes?
It stops accidental upgrades. Hashes also catch a changed file for the same version, which a plain pin does not.
Safeguard scans repositories for dependencies and generates SBOMs, and policies and gates can block risky changes in pull requests. Try a free scan at gold.safeguard.sh/scan. For model pinning, see pin model revisions like a lockfile.
Sources
Last reviewed: 19 October 2026.