SEC-26: Supply Chain Hardening — Enforcing package-lock.json and npm ci
SEC-26: Supply Chain Hardening — Enforcing package-lock.json and npm ci
Introduced in v1.0.60
Calmony Pay is a payment platform. A compromised dependency has a direct path to payment credential exfiltration or transaction tampering. This release closes SEC-26 by enforcing a committed lock file and reproducible installs across the entire build pipeline.
The Problem
Prior to v1.0.60, no package-lock.json was committed to the repository and CI used npm install. This created four concrete risks:
- Non-deterministic builds —
npm installresolves the highest compatible version at the time it runs. The versions installed on a developer's machine, in CI, and in production could all differ silently. - Supply chain attack surface — without pinned versions, a hijacked or typosquatted package on the npm registry can enter the build between runs with no visible change to any tracked file.
- Dependabot blocked — GitHub Dependabot requires a lock file to open automated version-bump PRs. Without one it cannot function.
- Divergent audit results —
npm auditin CI may flag different vulnerabilities than the actual production dependency tree if the resolved versions differ.
What Changed
.npmrc (new file)
A project-level .npmrc is now committed with three settings:
# Always generate and maintain a package-lock.json
package-lock=true
# Enforce SSL certificate validation for all registry requests
strict-ssl=true
# Pin to the public registry — prevents dependency confusion attacks
registry=https://registry.npmjs.org/
package-lock=truemakes it impossible to bypass lock file generation with--no-package-lock.strict-ssl=trueprevents SSL stripping on registry requests.- Explicitly pinning
registrycloses the dependency confusion attack vector, where a private package name could be hijacked on the public registry.
CI: lock-file-check job
A new job runs in parallel with the existing build job. If package-lock.json is absent from the repository, it fails with a detailed error:
SEC-26: package-lock.json is missing from the repository.
WHY THIS MATTERS:
1. Non-deterministic builds — npm install may resolve different versions
across developer machines, CI, and production.
2. Supply chain risk — without pinned versions, a hijacked package on
the npm registry can silently compromise the build.
3. Dependabot cannot open version-bump PRs without a lock file.
4. npm audit results may differ from the actual production dependency tree.
FIX:
Run 'npm install' locally, then commit the generated package-lock.json.
Once package-lock.json is present, the job verifies it is committed and reports its lockfileVersion.
CI: npm ci in the build job
The install step in the build job now selects the correct command based on whether a lock file exists:
- name: Install dependencies
run: |
if [ -f "package-lock.json" ]; then
echo "✓ package-lock.json found — using 'npm ci' for reproducible install"
npm ci
else
echo "⚠ No package-lock.json found — falling back to 'npm install'."
npm install
fi
npm ci differs from npm install in two important ways:
- It installs exactly the versions recorded in
package-lock.json— no version resolution. - It deletes
node_modulesbefore installing, preventing stale package accumulation.
Bootstrap: Action Required
The enforcement gate introduced by this release requires package-lock.json to exist. Until a developer commits it, the lock-file-check job will warn on every CI run.
To complete the bootstrap, run the following on the relevant branch:
npm install # generates package-lock.json
git add package-lock.json
git commit -m "chore: add package-lock.json for reproducible builds (SEC-26)"
git push
After this commit:
lock-file-checkwill pass cleanlybuildwill always usenpm ci- Dependabot will activate
- The
continue-on-error: trueflag onlock-file-checkinci.ymlshould be removed
Developer Guidelines
- Never use
--no-package-lock—.npmrcenforces this, but avoid it in scripts too. - Always commit
package-lock.json— treat it as a first-class source file, not a build artefact. - Run
npm cilocally (notnpm install) when you want to reproduce the exact CI environment. - Run
npm installonly when you intend to update or add dependencies, and always commit the resulting lock file changes in the same PR. - Do not add
package-lock.jsonto.gitignore— it must remain tracked.