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.
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.
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.
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.
FROM node:latest RUN npm install # Noncompliant
Use npm ci to install from the required lock file package-lock.json.
FROM node:latest RUN npm ci
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. |
FROM node:latest RUN yarn install # Noncompliant
Use yarn install --frozen-lockfile to install from the required lock file yarn.lock.
FROM node:latest RUN yarn install --frozen-lockfile
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. |
FROM node:latest RUN pnpm install # Noncompliant
Use pnpm install --frozen-lockfile to install from the required lock file pnpm-lock.yaml.
FROM node:latest RUN pnpm install --frozen-lockfile
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.
FROM oven/bun:latest RUN bun install # Noncompliant
Use bun install --frozen-lockfile to install from the required lock file bun.lock.
FROM oven/bun:latest RUN bun install --frozen-lockfile