npm — Lock Files & Security
package-lock.json is the backbone of reproducible npm installs. It records the exact version of every dependency (and every dependency's dependency) that was resolved when npm install was last run. Without it, different machines could resolve different dependency trees.
This guide covers lock file internals, version differences, npm audit for vulnerability scanning, and security best practices for managing dependencies.
package-lock.json is an auto-generated file that you should commit to version control. It contains a complete dependency graph with resolved versions, integrity hashes, and dependency relationships.
| 1 | // Simplified package-lock.json structure: |
| 2 | { |
| 3 | "name": "my-app", |
| 4 | "lockfileVersion": 3, |
| 5 | "packages": { |
| 6 | "": { |
| 7 | "name": "my-app", |
| 8 | "dependencies": { |
| 9 | "express": "^4.21.0" |
| 10 | } |
| 11 | }, |
| 12 | "node_modules/express": { |
| 13 | "version": "4.21.0", |
| 14 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", |
| 15 | "integrity": "sha512-...sha512-base64-hash...", |
| 16 | "dependencies": { |
| 17 | "accepts": "~1.3.8", |
| 18 | "array-flatten": "1.1.1" |
| 19 | } |
| 20 | }, |
| 21 | "node_modules/accepts": { |
| 22 | "version": "1.3.8", |
| 23 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", |
| 24 | "integrity": "sha512-...sha512-hash..." |
| 25 | } |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | // Key fields: |
| 30 | // lockfileVersion: Lock file format version (1, 2, or 3) |
| 31 | // packages: Map of all installed packages with metadata |
| 32 | // version: Resolved exact version |
| 33 | // resolved: URL of the package tarball |
| 34 | // integrity: Subresource Integrity hash (SRI) |
| 35 | // dependencies: The package's own dependencies |
info
npm has evolved the lock file format across major versions. Understanding the differences helps when working with different npm versions across projects.
| Version | npm Version | Format | Key Change |
|---|---|---|---|
| v1 | npm v5, v6 | Legacy (nested) | Original lock file, nested structure |
| v2 | npm v7 | Flat (packages key) | Flat structure with deterministic metadata |
| v3 | npm v8+ | Flat (packages key) | Removed bundled field, smaller file |
| 1 | # Check lock file version in your project: |
| 2 | head -5 package-lock.json |
| 3 | # { |
| 4 | # "name": "my-app", |
| 5 | # "lockfileVersion": 3, |
| 6 | |
| 7 | # npm v6 generates lockfileVersion: 1 |
| 8 | # npm v7 generates lockfileVersion: 2 |
| 9 | # npm v8+ generates lockfileVersion: 3 |
| 10 | |
| 11 | # npm v7+ can read v1 lock files (migrates on update) |
| 12 | # npm v6 cannot read v2/v3 lock files |
| 13 | |
| 14 | # Upgrade lock file version: |
| 15 | rm package-lock.json && npm install |
| 16 | # This generates the lock file for your current npm version |
| 17 | |
| 18 | # Downgrade lock file (if you need npm v6 compat): |
| 19 | npm install --lockfile-version 1 |
warning
npm ci (clean install) is designed for reproducible environments. It uses the lock file exclusively and fails if it is out of sync with package.json.
| 1 | # npm install: |
| 2 | # - Resolves dependency ranges |
| 3 | # - Updates package-lock.json |
| 4 | # - May install different versions than lock file |
| 5 | # - Can add new dependencies |
| 6 | # - Takes longer (resolution phase) |
| 7 | |
| 8 | # npm ci: |
| 9 | # - Installs EXACTLY from package-lock.json |
| 10 | # - Deletes node_modules first |
| 11 | # - Fails if package.json and lock file mismatch |
| 12 | # - Does NOT update lock file |
| 13 | # - 2-10x faster than npm install |
| 14 | # - Requires package-lock.json to exist |
| 15 | |
| 16 | # When npm ci fails: |
| 17 | $ npm ci |
| 18 | # npm ERR! cipm can only install packages |
| 19 | # when your package.json and package-lock.json |
| 20 | # are in sync. Please update your lock file. |
| 21 | |
| 22 | # Fix: run npm install locally and commit the lock file |
| 23 | npm install |
| 24 | git add package-lock.json |
| 25 | git commit -m "chore: update lock file" |
| 26 | git push |
| 27 | |
| 28 | # Then CI can run npm ci successfully |
best practice
npm audit scans your dependency tree against the npm Advisory database and reports known security vulnerabilities, along with their severity and remediation.
| 1 | # Run security audit |
| 2 | npm audit |
| 3 | |
| 4 | # Output example: |
| 5 | # === npm audit security report === |
| 6 | # |
| 7 | # ┌───────────────┬──────────────────────────────────┐ |
| 8 | # │ Low │ Prototype Pollution │ |
| 9 | # ├───────────────┼──────────────────────────────────┤ |
| 10 | # │ Package │ lodash │ |
| 11 | # ├───────────────┼──────────────────────────────────┤ |
| 12 | # │ Dependency of │ express │ |
| 13 | # ├───────────────┼──────────────────────────────────┤ |
| 14 | # │ Path │ express > lodash │ |
| 15 | # ├───────────────┼──────────────────────────────────┤ |
| 16 | # │ More info │ https://npmjs.com/advisories/... │ |
| 17 | # └───────────────┴──────────────────────────────────┘ |
| 18 | # |
| 19 | # found 1 low severity vulnerability |
| 20 | # run npm audit fix to fix them, or npm audit for details |
| 21 | |
| 22 | # Show only high and critical vulnerabilities: |
| 23 | npm audit --audit-level=high |
| 24 | |
| 25 | # JSON output for CI integration: |
| 26 | npm audit --json |
| 27 | |
| 28 | # Audit only production dependencies: |
| 29 | npm audit --production |
| 30 | |
| 31 | # Audit only devDependencies: |
| 32 | npm audit --only=dev |
| 33 | |
| 34 | # If audit returns non-zero exit code: |
| 35 | # 0 = no vulnerabilities |
| 36 | # 1 = vulnerabilities found (or error) |
| 37 | # Use in CI: npm audit --audit-level=high || true |
npm audit fix automatically installs compatible updates to resolve vulnerabilities. It respects SemVer ranges in your package.json.
| 1 | # Apply compatible security fixes |
| 2 | npm audit fix |
| 3 | # Only updates within existing range specifiers |
| 4 | |
| 5 | # Force fixes (may include breaking changes) |
| 6 | npm audit fix --force |
| 7 | # Updates to latest compatible version, |
| 8 | # even if it means breaking changes |
| 9 | |
| 10 | # Install semver-major updates (breaking) |
| 11 | npm audit fix --force --install-strategy=legacy |
| 12 | |
| 13 | # Dry run (see what would change) |
| 14 | npm audit fix --dry-run |
| 15 | |
| 16 | # Only fix production dependencies |
| 17 | npm audit fix --only=prod |
| 18 | |
| 19 | # Only fix devDependencies |
| 20 | npm audit fix --only=dev |
| 21 | |
| 22 | # Example output: |
| 23 | # npm audit fix |
| 24 | # fixed 3 of 5 vulnerabilities in 14323 scanned packages |
| 25 | # 2 vulnerabilities required semver-major updates |
| 26 | # run npm audit fix --force to fix them |
warning
npm fund displays funding information for your dependencies, showing how you can support the open-source projects your application depends on.
| 1 | # Show funding info for all dependencies |
| 2 | npm fund |
| 3 | |
| 4 | # Output example: |
| 5 | # my-app@1.0.0 |
| 6 | # ├── dependencies: |
| 7 | # │ ├── express |
| 8 | # │ │ └── https://opencollective.com/express |
| 9 | # │ ├── lodash |
| 10 | # │ │ └── https://github.com/sponsors/jdalton |
| 11 | # │ └── chalk |
| 12 | # │ └── https://github.com/chalk/chalk?sponsor=1 |
| 13 | # └── devDependencies: |
| 14 | # └── typescript |
| 15 | # └── https://github.com/sponsors/microsoft |
| 16 | |
| 17 | # Show funding for a specific package: |
| 18 | npm fund express |
| 19 | |
| 20 | # Open the funding URL in browser: |
| 21 | npm fund --open express |
| 22 | |
| 23 | # Include funding in your package.json: |
| 24 | { |
| 25 | "funding": { |
| 26 | "type": "individual", |
| 27 | "url": "https://github.com/sponsors/your-name" |
| 28 | } |
| 29 | } |
| 30 | // Or as a string: |
| 31 | { "funding": "https://github.com/sponsors/your-name" } |
Each entry in package-lock.json includes an integrity field containing a Subresource Integrity (SRI) hash. This ensures the downloaded package matches what was originally resolved, preventing tampering or corruption.
| 1 | // SRI integrity field in package-lock.json: |
| 2 | "node_modules/express": { |
| 3 | "version": "4.21.0", |
| 4 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", |
| 5 | "integrity": "sha512-qiIH1n0H3qRlF3TjehDdR2D7N6+0Zx1NMB2L1h/3rT6rDkKkHN8y2z5kf2UcjVqoA4H6hCL52OOpAVhvVq1w==" |
| 6 | } |
| 7 | |
| 8 | // The integrity field format: |
| 9 | // sha512-<base64-hash> |
| 10 | |
| 11 | // How npm uses SRI: |
| 12 | // 1. npm resolves the tarball URL |
| 13 | // 2. Downloads the tarball |
| 14 | // 3. Computes the SHA-512 hash |
| 15 | // 4. Compares with integrity field |
| 16 | // 5. If mismatch → installation fails |
| 17 | |
| 18 | // This protects against: |
| 19 | // - Registry compromise (modified tarball) |
| 20 | // - Man-in-the-middle attacks |
| 21 | // - Corrupted downloads |
| 22 | // - npm registry CDN issues |
| 23 | |
| 24 | // SRI is the reason npm install is considered secure |
info
Commit Lock Files
Always commit package-lock.json. It ensures reproducible builds across environments and protects against unexpected dependency updates. This is the single most important npm security practice.
Run npm audit Regularly
Run npm audit in CI and locally. Consider using GitHub Dependabot or Renovate to automatically create PRs for vulnerable dependencies.
Use npm ci in CI/CD
npm ci installs exactly from the lock file, ensuring CI never installs unexpected versions. It also catches lock file drift between environments.
Pin Versions for Production
Consider using --save-exact for production dependencies. Combined with the lock file, this provides maximum control over what runs in production.
Review Dependency Changes
Review package-lock.json changes during code reviews. Unexpected additions or removals of dependencies can indicate malicious packages or unintended side effects.
Always Commit package-lock.json
The lock file is essential for reproducible builds. Without it, every npm installcould resolve different versions, leading to "works on my machine" bugs.
Audit Dependencies Before Adding
Before adding a new dependency, check its download count, maintenance status, and security advisories. A package with 100 weekly downloads and no recent updates is a security risk.
Use npm Audit in CI
Add npm audit --audit-level=high to your CI pipeline. This prevents merging code with known high or critical vulnerabilities.
Keep npm Updated
Run npm install -g npm@latest periodically. Newer npm versions include security fixes, faster installs, and better lock file formats.