npm, yarn, pnpm, and bun allows declaring dependencies with version ranges rather than exact versions. When a project is built on a new system, the package manager performs dependency resolution: it queries the package registry and selects a specific version satisfying each declared range, then records the result in a lock file. Committing this lock file to source control ensures that all subsequent builds — on any machine or CI system — use exactly the same resolved versions.

Why is this an issue?

Without a committed lock file, dependency resolution runs anew on every fresh build, always selecting the latest version available that satisfies the declared range. This means different developers and CI systems may silently use different dependency versions, leaving the project exposed if a malicious version of a dependency is published within the allowed range.

Note, if you are authoring a library, an argument can be made to avoid lock files to allow detecting upstream dependency changes that could otherwise silently break your downstream consumers' builds. However, regularly updating your lock file ensures reproducible builds while still giving you visibility into upstream dependency changes.

What is the potential impact?

If an attacker publishes a malicious version of a dependency within your declared version range, an unlocked project may automatically adopt it on the next fresh build. This can result in arbitrary code execution, data exfiltration, or backdoors being silently introduced into your application as part of a supply chain attack.

How to fix it in npm

Installing dependencies is safe as long as a valid, up-to-date lock file is present. However, relying on this implicit behaviour means a stale or missing lock file can silently cause the package manager to resolve and download new versions, opening the door to dependency substitution attacks. Using a command that explicitly requires the lock file to exist and be respected makes the expectation clear and prevents accidental updates from slipping in unnoticed.

Code examples

Noncompliant code example

FROM node:latest

RUN npm install # Noncompliant

Compliant solution

Use npm ci to install from the required lock file package-lock.json.

FROM node:latest

RUN npm ci

How to fix it in Yarn

Installing dependencies is safe as long as a valid, up-to-date lock file is present. However, relying on this implicit behaviour means a stale or missing lock file can silently cause the package manager to resolve and download new versions, opening the door to dependency substitution attacks. Using a command that explicitly requires the lock file to exist and be respected makes the expectation clear and prevents accidental updates from slipping in unnoticed.

Note Yarn v2+ automatically enables immutable installs in CI environments via enableImmutableInstalls, but using --frozen-lockfile explicitly ensures this behavior regardless of environment detection.

Code examples

Noncompliant code example

FROM node:latest

RUN yarn install # Noncompliant

Compliant solution

Use yarn install --frozen-lockfile to install from the required lock file yarn.lock.

FROM node:latest

RUN yarn install --frozen-lockfile

How to fix it in PNPM

Installing dependencies is safe as long as a valid, up-to-date lock file is present. However, relying on this implicit behaviour means a stale or missing lock file can silently cause the package manager to resolve and download new versions, opening the door to dependency substitution attacks. Using a command that explicitly requires the lock file to exist and be respected makes the expectation clear and prevents accidental updates from slipping in unnoticed.

Note PNPM can automatically detect CI environments and enforce the lock file via the frozen-lockfile configuration, but using --frozen-lockfile explicitly ensures this behavior regardless of environment detection.

Code examples

Noncompliant code example

FROM node:latest

RUN pnpm install # Noncompliant

Compliant solution

Use pnpm install --frozen-lockfile to install from the required lock file pnpm-lock.yaml.

FROM node:latest

RUN pnpm install --frozen-lockfile

How to fix it in Bun

Installing dependencies is safe as long as a valid, up-to-date lock file is present. However, relying on this implicit behaviour means a stale or missing lock file can silently cause the package manager to resolve and download new versions, opening the door to dependency substitution attacks. Using a command that explicitly requires the lock file to exist and be respected makes the expectation clear and prevents accidental updates from slipping in unnoticed.

Code examples

Noncompliant code example

FROM oven/bun:latest

RUN bun install # Noncompliant

Compliant solution

Use bun install --frozen-lockfile to install from the required lock file bun.lock.

FROM oven/bun:latest

RUN bun install --frozen-lockfile

Resources