What Is Lock File Drift? A Hidden Risk in Dependency Management

By Cowboy Neil Education
TL;DR

Lock file drift occurs when your dependency manifest (package.json, requirements.txt, go.mod) and its lock file (package-lock.json, poetry.lock, go.sum) fall out of sync. The result: non-reproducible builds, silent security regressions, and SBOMs that don't reflect what's actually running. The fix is simple — strict install commands in CI and pre-commit hooks that catch drift before it reaches production.

A developer adds a new dependency to package.json, runs the application locally, confirms it works, and pushes the change. But they forget to run npm install to update package-lock.json. CI passes because it uses npm install (which silently regenerates the lock file) instead of npm ci (which would fail on the mismatch). The application deploys with a different set of dependency versions than anyone tested against. This is lock file drift — and it is one of the most common, least visible risks in modern dependency management.

What Is a Lock File?

Every modern package manager uses two files to manage dependencies:

  1. A manifest — the file where developers declare what they need (package.json, pyproject.toml, Cargo.toml, go.mod, Gemfile, composer.json). Manifests typically specify version ranges (e.g., ^1.2.0), giving the package manager flexibility to resolve compatible versions.

  2. A lock file — the file generated by the package manager that records the exact versions resolved for every direct and transitive dependency (package-lock.json, poetry.lock, Cargo.lock, go.sum, Gemfile.lock, composer.lock, pnpm-lock.yaml). Lock files ensure that every environment installs identical versions.

The manifest says what you want. The lock file says what you got. When these two diverge, you have lock file drift.

How Drift Happens

Lock file drift is usually accidental. The most common causes:

Manual manifest edits without reinstalling. A developer adds or bumps a dependency version in the manifest but does not run the package manager to regenerate the lock file. The manifest now declares constraints that the lock file does not satisfy.

Merge conflicts resolved incorrectly. Two branches modify dependencies independently. When merged, the manifest may reflect both changes, but the lock file — which is often large, machine-generated, and difficult to review — may be resolved incorrectly or left in an inconsistent state.

Automated tools that update one file but not the other. CI scripts, Dockerfiles, or custom tooling may modify the manifest (e.g., bumping a version) without running the corresponding install command to propagate the change to the lock file.

Different package manager versions. Different developers or CI environments running different versions of the same package manager can produce structurally different lock files from the same manifest, even when the resolved versions are identical.

Why Lock File Drift Is Dangerous

Non-Reproducible Builds

The entire purpose of a lock file is build reproducibility. When drift occurs, the lock file no longer accurately describes what gets installed. Different environments may resolve different versions depending on when they run, what’s available in the registry, and which resolution strategy the package manager uses. The result: “works on my machine” failures, intermittent test breakage, and production behavior that nobody tested.

Silent Security Regressions

Lock file drift can prevent security patches from being applied or introduce vulnerable versions without anyone noticing.

Consider a scenario where the manifest is updated to require library >= 2.1.0 (which patches a CVE), but the lock file still pins [email protected] (the vulnerable version). If CI uses a lenient install command, it may honor the lock file and install the vulnerable version. The developer believes the patch is applied; it is not.

The reverse is also dangerous: the lock file may resolve to a newer version that introduces breaking changes or new vulnerabilities that the manifest’s range technically allows but that no one has tested.

Inaccurate SBOMs

This is where lock file drift intersects directly with software supply chain security. When you generate an SBOM from your project, most SBOM generation tools read the lock file to determine the exact versions of your dependencies. If the lock file is drifted, the SBOM is wrong — it lists versions that do not match what is actually installed in the built artifact.

An inaccurate SBOM undermines every downstream process that depends on it: vulnerability scanning misses real exposures or flags false positives, KEV monitoring operates on stale data, and compliance attestations become unreliable. For organizations using sbomify or similar platforms to manage and monitor SBOMs, lock file drift is a data quality problem at the source.

Detecting Drift by Ecosystem

Each package manager has mechanisms to detect lock file drift. The key principle: use strict install commands in CI that fail when the lock file doesn’t match the manifest.

Node.js (npm / Yarn / pnpm)

# npm: fails if package-lock.json is out of sync with package.json
npm ci

# Yarn: fails if yarn.lock needs updating
yarn install --frozen-lockfile

# pnpm: fails if pnpm-lock.yaml needs updating
pnpm install --frozen-lockfile

npm ci is the single most important command for preventing Node.js lock file drift. Unlike npm install, it does not modify the lock file — it installs exactly what the lock file specifies, and fails if the lock file is inconsistent with the manifest.

Python (pip / Poetry / Pipenv)

# Poetry: fails if poetry.lock is out of date
poetry check --lock

# Pipenv: verify lock file integrity
pipenv verify

# pip with hashes: fails if installed packages don't match pinned hashes
pip install --require-hashes -r requirements.txt

Rust (Cargo)

# Check if Cargo.lock is up to date (in CI)
cargo check --locked

The --locked flag tells Cargo to refuse to update the lock file. If Cargo.toml and Cargo.lock are out of sync, the command fails.

Go

# Tidy modules and check for changes
go mod tidy
git diff --exit-code go.sum

Go does not have a single “strict mode” flag, but running go mod tidy followed by checking whether go.sum changed is the standard CI pattern.

Ruby (Bundler)

# Fails if Gemfile.lock needs updating
bundle install --frozen

Preventing Drift

CI Pipeline Checks

The most effective prevention is a CI step that runs the strict install command and fails the build on drift. This should be the first step in your pipeline — before tests, before linting, before anything that depends on installed packages.

# Example: GitHub Actions step for Node.js
- name: Check for lock file drift
  run: npm ci

Pre-Commit Hooks

Add a pre-commit hook that regenerates the lock file and checks for changes:

#!/bin/sh
# Regenerate lock file and fail if it changed
npm install --package-lock-only
git diff --exit-code package-lock.json || {
  echo "Lock file drift detected. Run 'npm install' and commit the updated lock file."
  exit 1
}

Automated Dependency Updates

Tools like Dependabot and Renovate update both the manifest and lock file together in a single pull request, eliminating the most common source of drift.

Pull Request Review Discipline

Require that any PR modifying a dependency manifest also includes the corresponding lock file update. Code review tools can flag PRs that change package.json without changing package-lock.json.

Lock File Drift and SBOM Accuracy

For organizations subject to software supply chain regulations or generating SBOMs for compliance with the EU Cyber Resilience Act, lock file drift is not just a developer inconvenience — it is a compliance risk.

An SBOM generated from a drifted lock file is effectively a false attestation: it claims the software contains specific component versions when it may actually contain different ones. This matters for:

  • Vulnerability monitoring — Scanners checking your SBOM against CVE and KEV databases will produce incorrect results if the SBOM doesn’t match the deployed artifact.
  • Audit trails — Compliance frameworks expect SBOMs to accurately represent what is shipped. A drifted SBOM fails this requirement.
  • Incident response — When a new vulnerability is disclosed, you need to know exactly what versions are in your software. A drifted lock file makes this impossible.

The fix is straightforward: ensure that CI enforces lock file consistency before the SBOM generation step. If the lock file is accurate, the SBOM will be accurate.

Frequently Asked Questions

What is lock file drift?

Lock file drift is the condition where a project’s dependency lock file (such as package-lock.json, poetry.lock, or Cargo.lock) is out of sync with its primary dependency manifest (such as package.json, pyproject.toml, or Cargo.toml). This means the versions recorded in the lock file do not match the constraints in the manifest, leading to unreproducible builds and potential security issues.

Why does lock file drift matter for security?

Lock file drift can silently downgrade dependencies to versions with known vulnerabilities, or allow untested versions to enter production. It also makes SBOMs inaccurate — if your SBOM is generated from a drifted lock file, it does not reflect what is actually installed in your application, undermining vulnerability monitoring and compliance.

How do I detect lock file drift?

Most package managers offer strict install commands that fail when drift is detected: npm ci for Node.js, poetry check --lock for Python, cargo check --locked for Rust, and go mod tidy followed by checking for changes in Go. Run these in CI to catch drift automatically.

How do I prevent lock file drift?

Use strict install commands in CI (npm ci instead of npm install), add pre-commit hooks that regenerate the lock file and fail if it changes, require lock file updates in pull request review checklists, and use automated dependency update tools like Dependabot or Renovate that update both files together.

Found an error or typo? File a PR against this file.