lockfile-sentinel
v0.1.1
Published
Security scanner for package-lock.json and pnpm-lock.yaml to detect malicious dependency changes
Maintainers
Readme
lockfile-sentinel 🛡️
A security scanner for package-lock.json and pnpm-lock.yaml that detects suspicious dependency changes before they land in production.
Inspired by the March 2026 Axios vulnerability, lockfile-sentinel analyzes package changes with three powerful detection layers:
- Blast Radius Report - Track exactly how many transitive dependencies changed
- New Maintainer Alert - Cross-reference npm metadata to flag suspicious account access
- Entropy Check - Detect obfuscated or unusually compressed code in dependencies
The Problem
In late March 2026, malicious versions of Axios were pushed to npm, bypassing standard CI checks because they looked like legitimate updates. Developers had no visibility into:
- How many transitive dependencies were pulled in
- Whether the maintainer was newly added
- Whether code was suspiciously obfuscated
lockfile-sentinel solves this by analyzing behavioral anomalies in your lockfile changes.
Installation
npm install -g lockfile-sentinelOr in your project:
npm install --save-dev lockfile-sentinelQuick Start
CLI Usage
Scan your current lockfile against the previous commit:
lockfile-sentinel scanWith custom thresholds:
lockfile-sentinel scan \
--threshold 75 \
--blast-radius 40 \
--entropy 6.5 \
--fail-on-findingsSpecify custom base and head files:
lockfile-sentinel scan package-lock.json.old package-lock.jsonOutput as JSON for CI/CD integration:
lockfile-sentinel scan --jsonAnalyze Entropy of a File
lockfile-sentinel analyze-entropy src/suspicion.jsInitialize GitHub Actions
lockfile-sentinel initThis creates .github/workflows/lockfile-sentinel.yml to automatically check lockfile changes on every PR.
Architecture
Phase A: Drift Analysis
The tool builds a dependency graph from both lockfile versions and compares tree structure.
Blast Radius Metric: If updating 1 package changes 45+ transitive dependencies, that's high-risk.
Phase B: Metadata Verification
For every new/updated package, the tool:
- Queries
registry.npmjs.org/<package> - Checks maintainer history and publish timestamps
- Flags if the publisher was recently added to the project
Phase C: Entropy Analysis
High-entropy files (>6.5) often indicate:
- Compression/minification hiding malicious code
- Encrypted payloads
- Binary data where text code should be
Shannon Entropy Formula: $$H(X) = -\sum_{i=1}^{n} P(x_i) \log_2 P(x_i)$$
Configuration
Command Line Options
--threshold <number> Risk threshold (0-100, default: 80)
--blast-radius <number> Blast radius threshold (default: 45)
--entropy <number> Entropy threshold (default: 6.5)
--json Output as JSON
--fail-on-findings Exit with code 1 if findings detectedRisk Score Calculation
The tool calculates a risk score (0-100) based on:
- Base score (0-30): Number of packages changed
- Critical findings (+35 each): Likely malicious patterns
- High findings (+15 each): Suspicious behavior
- Medium findings (+5 each): Worth investigating
Programmatic Usage
const { LockfileSentinel } = require('lockfile-sentinel');
const sentinel = new LockfileSentinel();
const result = await sentinel.scan(
'package-lock.json.old',
'package-lock.json',
{
blastRadius: 45,
entropyThreshold: 6.5,
maintainerAgeHours: 24
}
);
console.log(result);
// {
// timestamp: Date,
// baseFile: string,
// headFile: string,
// totalChanges: number,
// findings: SecurityFinding[],
// riskScore: number,
// blastRadius: Map<string, number>,
// summary: {...}
// }Security Finding Types
1. Blast Radius
Type: blast_radius
When: A single package update triggers 45+ transitive dependency changes
{
"type": "blast_radius",
"severity": "high|critical",
"package": "axios",
"message": "Large blast radius: 67 transitive dependencies affected",
"evidence": {
"packageName": "axios",
"oldVersion": "1.5.0",
"newVersion": "1.6.2",
"transitiveDepsChanged": 67
}
}2. New Maintainer Alert
Type: new_maintainer
When: Package published by user who recently gained access
{
"type": "new_maintainer",
"severity": "high",
"package": "follow-redirects",
"message": "Suspicious activity detected: Published only 5 minutes ago",
"evidence": {
"packageName": "follow-redirects",
"version": "1.14.9",
"reasons": ["Published only 5 minutes ago"],
"maintainers": [{"name": "newuser123", "email": "..."}]
}
}3. Entropy Detection
Type: entropy
When: File entropy > 6.5 (indicates compression/obfuscation)
{
"type": "entropy",
"severity": "high",
"package": "malicious-lib",
"message": "High entropy detected in minified files (7.2)",
"evidence": {
"filename": "lib/index.js",
"entropy": 7.2,
"fileSize": 45000
}
}GitHub Actions Integration
Create .github/workflows/lockfile-sentinel.yml:
name: Security - Lock File Sentinel
on:
pull_request:
paths:
- 'package-lock.json'
- 'pnpm-lock.yaml'
jobs:
security-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install lockfile-sentinel
run: npm install -g lockfile-sentinel
- name: Run security scan
run: |
git show HEAD:package-lock.json > /tmp/base-lock.json || true
lockfile-sentinel scan /tmp/base-lock.json package-lock.json \
--fail-on-findings \
--threshold 75The action will:
- Fetch the base lockfile from the previous commit
- Compare it against the PR's lockfile
- Fail the check if findings are detected or risk score exceeds threshold
Implementation Details
Lockfile Parsers
package-lock.json: Native JSON parsingpnpm-lock.yaml: YAML-like structure parsingyarn.lock: Yarn format parsing
Dependency Graph Algorithm
Uses Depth-First Search (DFS) to:
- Build transitive dependency tree
- Calculate the "blast radius" of changes
- Track each package's entire dependency chain
Registry API Integration
Queries https://registry.npmjs.org/<package> to:
- Fetch maintainer lists
- Check publish timestamps
- Verify package metadata integrity
Entropy Calculation
Implements Shannon Entropy:
- Counts character frequencies
- Calculates probability distribution
- Returns entropy score (0-8)
Typical Ranges:
- 3.5 - 5.0: Normal JavaScript
- 5.0 - 6.5: Minified code
- 6.5+: Compressed/obfuscated/binary
Testing
npm testRun tests with coverage:
npm test -- --coveragePerformance
- Parsing: <100ms for typical lockfiles
- Diffing: <50ms for <1000 packages
- Registry Queries: ~100-500ms depending on package count (rate-limited to avoid npm abuse)
- Total Scan: ~2-5 seconds for typical PRs
Limitations
Historical Maintainer Data: npm API doesn't provide exact timestamps for when maintainers were added. We infer from version publish times.
Tarball Analysis: Full tarball extraction and analysis is slow. Current implementation samples the beginning of tarballs.
Rate Limiting: npm registry has rate limits (~100 requests/minute). Large updates may hit these limits.
Obfuscation Detection: Entropy-based detection has false positives (legitimate minification can look suspicious).
FAQ
Q: Will this slow down my CI pipeline?
A: No. Typical scans complete in 2-5 seconds with network latency being the primary factor.
Q: Can I use this for private npm registries?
A: Yes! Modify the registry URL in registry-scout.ts.
Q: What if I get false positives?
A: You can:
- Whitelist packages:
--ignore axios,lodash - Adjust thresholds:
--entropy 7.0 - Review findings and add to
.lockfile-sentinel-ignore
Q: Does this replace vulnerability scanners like Snyk?
A: No! This is complementary. Snyk checks for known vulnerabilities. Lockfile Sentinel detects behavioral anomalies that might be zero-day attacks.
Contributing
Issues and PRs welcome!
License
MIT - See LICENSE file
Security Notice
This tool is designed to enhance supply chain security, but it's not a silver bullet. Always:
- Review dependency changes manually
- Keep up with security advisories
- Use multiple layers of security validation
