Using npm install in CI instead of npm ci means the lockfile is advisory rather than enforced — if package.json and the lockfile diverge (a common accident after a manual install), CI installs different versions than the lockfile specifies, silently breaking reproducibility. SLSA L2 and SSDF SP 800-218 PW.4.1 both require that the build process is deterministic and traceable. A non-reproducible build makes security audits meaningless: you cannot verify what code shipped without knowing exactly which dependency versions were installed.
Info because non-frozen CI installs are a build hygiene defect that undermines reproducibility guarantees, but they rarely cause immediate functional failures unless a transitive dependency changes in a breaking way.
Replace npm install with npm ci in all CI configuration. npm ci errors if the lockfile and package.json are out of sync, enforcing that the lockfile is the authoritative source.
# .github/workflows/ci.yml
- name: Install dependencies
run: npm ci
For Docker builds, copy the lockfile before installing:
COPY package.json package-lock.json ./
RUN npm ci --only=production
For Yarn: yarn install --frozen-lockfile. For pnpm: pnpm install --frozen-lockfile.
ID: code-quality-essentials.dependencies.build-reproducible
Severity: info
What to look for: Check whether the CI pipeline uses a frozen lockfile install command (npm ci, yarn install --frozen-lockfile, pnpm install --frozen-lockfile, bun install --frozen-lockfile) rather than npm install. The difference matters: npm install may resolve newer patch versions even with a lockfile present, while npm ci installs exactly what the lockfile specifies and errors if the lockfile is out of sync with package.json. Also check whether the lockfile is committed (covered by a separate check). Look for npm ci in CI workflow files. Additionally check for Docker build caching patterns that ensure the lockfile is the cache key.
Pass criteria: Enumerate all relevant code locations. Lockfile committed to repo (pass from earlier check) AND CI uses npm ci (or equivalent frozen install) rather than npm install with at least 1 verified location.
Fail criteria: CI uses npm install instead of frozen lockfile install, OR no CI configured.
Skip (N/A) when: Not a Node.js project.
Detail on fail: "CI uses 'npm install' instead of 'npm ci'; builds may not be reproducible if package.json and lockfile diverge"
Remediation: Replace npm install with npm ci in all CI configuration:
# .github/workflows/ci.yml
- name: Install dependencies
run: npm ci # uses lockfile exactly, errors if out of sync
For Docker:
COPY package.json package-lock.json ./
RUN npm ci --only=production