Lockfile integrity analyzer for supply chain security
npm audit and Snyk find CVEs. Nobody checks if your lockfile itself is healthy.
Lockfiles are the foundation of reproducible builds, yet they accumulate structural problems that vulnerability scanners completely ignore:
- Phantom dependencies -- packages in your lockfile that no manifest dependency requires. Leftover from removed packages, they bloat installs and expand your attack surface.
- Manifest drift -- your lockfile resolves versions outside the ranges declared in
package.json. This meansnpm installandnpm ciproduce different results. - Duplicate resolutions -- the same package resolved to 3+ versions. Bundle size bloat, subtle bugs from multiple instances of the same library.
- Missing integrity hashes -- packages without SRI hashes can be silently replaced by a compromised registry.
- Registry anomalies -- dependencies resolved from unexpected registries. A classic supply chain attack vector.
- Install scripts in transitive deps --
postinstallscripts in deep dependencies are how most supply chain attacks execute.
lockcheck catches all of these across npm, yarn (classic + berry), pnpm, poetry, and Go modules.
# Install
npm install -g lockcheck
# Run in any project directory
lockcheck check
# Strict mode (warnings = errors)
lockcheck check --strict
# JSON output for CI
lockcheck check --format json
# SARIF output for GitHub Security tab
lockcheck check --format sarif
# Compare two lockfiles
lockcheck diff package-lock-old.json package-lock-new.json
# Lockfile statistics
lockcheck stats| Check | Severity | Description |
|---|---|---|
phantom-dependency |
warning | Package in lockfile not traceable to any manifest dependency |
manifest-drift |
error | Lockfile version outside manifest's declared semver range |
duplicate-resolution |
info/warning | Same package resolved to multiple versions |
missing-integrity |
warning/error | Missing or malformed integrity hash |
registry-anomaly |
warning | Package resolved from untrusted registry |
install-script |
info/warning | Package has install scripts (higher severity for transitive deps) |
| Lockfile | Manifest | Ecosystem |
|---|---|---|
package-lock.json (v1/v2/v3) |
package.json |
npm |
yarn.lock (classic) |
package.json |
Yarn 1.x |
yarn.lock (berry) |
package.json |
Yarn 2+ |
pnpm-lock.yaml |
package.json |
pnpm |
poetry.lock |
pyproject.toml |
Python Poetry |
go.sum |
go.mod |
Go Modules |
Options:
-l, --lockfile <path> Path to lockfile (auto-detect if not specified)
-s, --strict Strict mode - warnings become errors
-f, --format <format> Output format: text, json, sarif (default: text)
--skip-checks <checks> Comma-separated checks to skip
--ignore-packages <packages> Comma-separated packages to ignore
--trusted-registries <registries> Comma-separated trusted registries
Compare two lockfiles and show added, removed, and changed packages.
Options:
-f, --format <format> Output format: text, json (default: text)
Show lockfile statistics: package count, integrity coverage, duplicates, etc.
Options:
-l, --lockfile <path> Path to lockfile (auto-detect if not specified)
-f, --format <format> Output format: text, json (default: text)
| Code | Meaning |
|---|---|
| 0 | Clean -- no errors or warnings |
| 1 | Errors found (or warnings in strict mode) |
| 2 | Warnings found (non-strict mode) |
Create .lockcheckrc or .lockcheckrc.json in your project root:
{
"strict": false,
"format": "text",
"skipChecks": ["install-script"],
"ignorePackages": ["fsevents"],
"trustedRegistries": ["registry.npmjs.org", "npm.pkg.github.com"]
}- name: Check lockfile integrity
run: npx lockcheck check --strict --format sarif > lockcheck.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: lockcheck.sarif# .husky/pre-commit
npx lockcheck check --strictimport { Analyzer, detectParser, getManifestFilename } from 'lockcheck';
import * as fs from 'fs';
// Parse lockfile
const lockContent = fs.readFileSync('package-lock.json', 'utf-8');
const parser = detectParser(lockContent, 'package-lock.json');
const lockfile = parser.parseLockfile(lockContent);
// Parse manifest
const manifestContent = fs.readFileSync('package.json', 'utf-8');
const manifest = parser.parseManifest(manifestContent);
// Analyze
const analyzer = new Analyzer();
const report = analyzer.analyze(lockfile, manifest, 'package-lock.json', {
strict: true,
ignorePackages: ['fsevents'],
});
console.log(`Risk score: ${report.summary.riskScore}/100`);
console.log(`Errors: ${report.summary.errors}`);
console.log(`Warnings: ${report.summary.warnings}`);
// Diff two lockfiles
const oldLock = parser.parseLockfile(fs.readFileSync('old.lock', 'utf-8'));
const newLock = parser.parseLockfile(fs.readFileSync('new.lock', 'utf-8'));
const diff = analyzer.diff(oldLock, newLock);
// Stats
const stats = analyzer.stats(lockfile);lockcheck analysis report
==================================================
Lockfile: package-lock.json
Format: npm
Packages: 847
Risk: 35/100
Found: 2 error(s), 5 warning(s), 3 info(s)
[manifest-drift]
[x] ERROR: Package "lodash" locked at 3.10.1 but manifest requires ^4.17.0
[x] ERROR: Package "debug" is declared in manifest but missing from lockfile
[phantom-dependency]
[!] WARN: Package "leftpad" is in lockfile but not traceable to any manifest dependency
[!] WARN: Package "event-stream" is in lockfile but not traceable to any manifest dependency
[registry-anomaly]
[!] WARN: Package "malicious-pkg@1.0.0" is resolved from untrusted registry: evil-npm.io
[duplicate-resolution]
[-] INFO: Package "debug" is resolved to 3 different versions: 2.6.9, 3.2.7, 4.3.4
[install-script]
[!] WARN: Transitive dependency "esbuild@0.19.0" has install scripts
[!] WARN: Transitive dependency "sharp@0.33.0" has install scripts
==================================================
RESULT: FAIL
| Feature | npm audit | Snyk | socket.dev | lockcheck |
|---|---|---|---|---|
| CVE detection | Yes | Yes | Yes | No |
| Phantom dependencies | No | No | No | Yes |
| Lockfile-manifest drift | No | No | No | Yes |
| Duplicate resolution | No | No | Partial | Yes |
| Integrity hash check | No | No | No | Yes |
| Multi-ecosystem | npm only | Multi | Multi | Multi |
| Offline capable | No | No | No | Yes |
| SARIF output | No | Yes | No | Yes |
| Zero config | Yes | No | No | Yes |
lockcheck complements vulnerability scanners. Run npm audit for CVEs, run lockcheck for structural integrity.
src/
cli/ # CLI entry point and commands
core/
analyzer.ts # Analysis orchestrator
scoring.ts # Risk scoring engine
checks/ # Individual check modules
parsers/ # Lockfile format parsers (npm, yarn, pnpm, poetry, go)
formatters/ # Output formatters (text, JSON, SARIF)
config/ # Configuration loader
utils/ # Semver and hash utilities
MIT