lockwarden drift
Lockfile & version-anomaly detection vs a base ref.
Synopsis
Section titled “Synopsis”lockwarden drift [--base <ref>]Usage: lockwarden drift [options]
lockfile & version-anomaly detection vs a base ref
Options: --base <ref> git ref to compare the lockfile against (default: "main") -h, --help display help for commanddrift answers: did my lockfile change in ways my manifest doesn’t explain? It
compares the working lockfile against the one committed at --base and flags anomalies —
the tampering patterns that never show up in a normal dependency bump.
| Flag | Type | Default | Meaning |
|---|---|---|---|
--base <ref> | string | main | Git ref to compare the lockfile against |
All global flags apply.
What it detects
Section titled “What it detects”| Finding kind | Severity | What it means |
|---|---|---|
integrity-swap | critical | The integrity hash changed for an unchanged version — the same name@version now points at different bytes |
unexplained-version | high | A resolved version changed with no corresponding package.json change to explain it — lockfile-only tampering |
resolved-url-move | high (host move) / med | The tarball URL for an unchanged version moved — high when the host changed |
patch-introduced-dep | high | A new package entered the tree alongside patch/minor bumps of existing dependencies |
Example 1 — tampered lockfile
Section titled “Example 1 — tampered lockfile”npx lockwarden drift --base maindrift vs 'main' — critical 1 · high 1lockfile: package-lock.json
[critical] integrity-swap nested-lib@3.0.1 integrity hash changed for unchanged version nested-lib@3.0.1 baseIntegrity: sha512-N1yzAAA0aaaBBBbbbCCCcccDDDdddEEEeeeFFFfffGGGgggHHHhhhIIIiiiJJJjjjKKKkkkLLLlllMMAA== currentIntegrity: sha512-TAMPEREDaaaBBBbbbCCCcccDDDdddEEEeeeFFFfffGGGgggHHHhhhIIIiiiJJJjjjKKKkkkLLLlllMMAA==
[high] resolved-url-move other-lib@2.0.0 other-lib@2.0.0 tarball host moved registry.npmjs.org → registry.evil-mirror.example for an unchanged version baseResolved: https://registry.npmjs.org/other-lib/-/other-lib-2.0.0.tgz currentResolved: https://registry.evil-mirror.example/other-lib/-/other-lib-2.0.0.tgz
note: provenance is informational only — valid provenance has shipped from compromised pipelinesExit 1. Neither of these happens in a legitimate bump: same version + different hash
means different bytes; same version + different host means a different source of truth.
Example 2 — a dependency PR that smuggles a package in
Section titled “Example 2 — a dependency PR that smuggles a package in”npx lockwarden drift --base maindrift vs 'main' — high 1lockfile: package-lock.json
[high] patch-introduced-dep dep-b@1.0.0 new package dep-b@1.0.0 entered the tree alongside patch/minor bump(s): dep-a 1.0.0 → 1.0.1 bumps: dep-a 1.0.0 → 1.0.1
note: provenance is informational only — valid provenance has shipped from compromised pipelinesA patch release that brings a new dependency with it is the axios/plain-crypto-js
delivery shape. audit --diff scores the same event from the
execution-surface side (LW006D-PATCH-DEP-INTRODUCED, Critical) — run both on
dependency PRs; see dependency review.
Example 3 — clean
Section titled “Example 3 — clean”npx lockwarden drift --base origin/maindrift vs 'origin/main' — no findingslockfile: package-lock.json clean no lockfile drift vs 'origin/main'
note: provenance is informational only — valid provenance has shipped from compromised pipelinesExit 0. Other useful refs:
npx lockwarden drift --base v1.4.0 --json # since the last release, machine-readablenpx lockwarden drift --base HEAD~5 # the last five commits--json output
Section titled “--json output”{ "command": "drift", "base": "main", "lockfile": { "path": "/work/app/package-lock.json", "type": "npm" }, "findings": [ { "kind": "integrity-swap", "severity": "critical", "package": "nested-lib@3.0.1", "detail": "integrity hash changed for unchanged version nested-lib@3.0.1", "evidence": { "baseIntegrity": "sha512-N1yz…", "currentIntegrity": "sha512-TAMP…" } } ], "warnings": [], "exitCode": 1}Field tables: JSON output → drift.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 | No anomalies at or above --threshold |
1 | Anomalies at or above --threshold |
2 | Execution error — unknown ref, lockfile missing at the base ref, unparseable lockfile |
A note on provenance
Section titled “A note on provenance”drift deliberately does not treat SLSA provenance as a pass signal — every run
prints the reminder. The June 2026 Miasma compromise published trojanized packages with
valid provenance from the victim’s own hijacked pipeline. Provenance presence is
informational context, never a green light — version-to-version anomaly is the honest
signal. More: trust model.
driftreads git locally; it makes no network calls.--offlineis always satisfied.- Requires the lockfile to exist at the base ref (exit
2otherwise).
See also
Section titled “See also”audit --diff— scores what changed packages can newly execute;driftcatches changes to the lockfile itself.- Dependency review — both commands in the PR workflow.
- Incident response —
driftas a post-incident tamper sweep.