No. They stop drift and tampering after you lock, not a bad version you already locked. npm ci and pip hash checking make installs repeatable and verifiable, so a changed file fails the build. If your lockfile already names the compromised release, they will install it faithfully every time.
What each control does
npm ci. The npm documentation describes it as meant for automated environments. It needs an existing package-lock.json or npm-shrinkwrap.json, exits with an error if the lock does not match package.json instead of updating it, removes an existing node_modules folder before it starts, and never writes to package.json or the lock files. Installs are essentially frozen.
pip hash checking. The pip documentation says hash-checking mode uses hashes embedded in a requirements file to protect against remote tampering and network issues. It is all or nothing: a hash on any requirement switches the mode on for all of them, and every requirement needs a hash, because a partially hashed file would leave a gap. You can force the mode with the require-hashes option, and pip can display the hashes of packages it fetched to help you bootstrap the list.
pip install --require-hashes -r requirements.txt
What they protect against
| Threat | Frozen install | Hash checking |
|---|---|---|
| A new malicious release appears after you locked | Not installed, since the lock names the old one | Not installed |
| A file on the registry or mirror is altered after you locked | Integrity check in the lock can catch a changed tarball | Hash mismatch fails the install |
| A resolver picks a newer version through a loose range | Prevented, because the lock is followed | Prevented, because every version is pinned |
| A man in the middle swaps a download | Integrity check can catch it | Hash mismatch fails the install |
These are real gains, and they are the reason both controls belong in CI.
What they do not protect against
| Threat | Why not |
|---|---|
| The bad version is already in the lock | The lock is the instruction to install it |
| You regenerate the lock and it picks the bad release | The new lock reflects the registry at that moment |
| A trusted maintainer account publishes a bad version and you upgrade on purpose | The hash is correct for what was published |
| Malicious code that behaves differently by environment | Hashes confirm identity, not behavior |
A hash proves you got the file you expected. It says nothing about whether that file is safe.
The moments that matter
The dangerous moments are the ones where the lock changes: a routine update, a bot pull request, a new dependency, a developer running a plain install and committing the result. Put controls there.
- Review lock diffs as code. Ask what changed, why, and whether the new version is recent.
- Add a policy check on new or changed versions, so a flagged version fails the pull request.
- Consider a short delay before adopting brand new releases, a tradeoff covered in dependency cooldown posts.
- After an advisory, search lockfiles for the bad version and update the lock deliberately, then rebuild with a frozen install.
Using them during an incident
When you roll back, use both controls to make the rollback stick. Pin the known good version, regenerate the lock in a clean environment, commit it, and let CI run the frozen install so any accidental drift fails loudly. The guide on rolling back to a known good version walks through it.
Quick check
Is npm ci safer than npm install?
For repeatability, yes, because it follows the lock exactly and fails on mismatch. It does not judge whether the locked versions are safe.
Do I need hashes if I pin exact versions?
Exact pins fix the version, hashes fix the file. Hash mode adds protection against a changed or swapped file.
Safeguard can help with the inventory side of this work. SBOM and dependency scanning show where a package and version appear across your projects, and policies and gates can stop a flagged version at the pull request. You can try a free scan at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.