Build reproducibility verified
Why it matters
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.
Severity rationale
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.
Remediation
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.
Detection
-
ID:
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 thannpm install. The difference matters:npm installmay resolve newer patch versions even with a lockfile present, whilenpm ciinstalls exactly what the lockfile specifies and errors if the lockfile is out of sync withpackage.json. Also check whether the lockfile is committed (covered by a separate check). Look fornpm ciin 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 thannpm installwith at least 1 verified location. -
Fail criteria: CI uses
npm installinstead 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 installwithnpm ciin all CI configuration:# .github/workflows/ci.yml - name: Install dependencies run: npm ci # uses lockfile exactly, errors if out of syncFor Docker:
COPY package.json package-lock.json ./ RUN npm ci --only=production
External references
- slsa:1.0 · L2 — SLSA Level 2 — Hosted build platform with tamper resistance
- ssdf:800-218 · PW.4.1 — Reuse existing, well-secured software where feasible
Taxons
History
- 2026-04-18·v1.0.0·Initial import from code-quality-essentials·automated