The problem
An attacker who holds an npm token or a maintainer's machine, but not the project's CI pipeline, publishes by hand. The malicious version then has no provenance, while earlier versions do. This is what happened with axios 1.14.1 and 0.30.4 in March 2026.
pnpm can reject such a version at install time with trustPolicy: no-downgrade.
npm cannot:
- npm/cli#9242 asks for the same setting. It has 68 reactions and has been open since April 2026.
- PR #9902 implements it, has had no activity since September, and its approach is disputed.
- npq detects a provenance regression, but
according to its README it covers only the direct dependencies in
package.jsonand does not read lockfiles.
How the tool works
npm-trust-downgrade reads
package-lock.json, fetches the registry metadata of each package and compares
each locked version with the history of that package. There are three evidence
levels: trusted publisher, provenance attestation and none. It has no runtime
dependencies, which matters for a tool in this position.
A version is reported when a release that is both published earlier and lower in semver order has stronger evidence. pnpm compares by date alone. The semver condition follows an objection from the discussion of PR #9902: a maintenance release of an old line should not be measured against a newer line that adopted provenance later.
pino@9.13.1 has a transitive dependency that shows the output:
$ npx --yes github:Jwrede/npm-trust-downgrade#v0.1.0
DOWNGRADE slow-redact@0.3.2
has: none; 0.3.1 had: trusted publisher
0.3.2 published 2025-10-11 by matteo.collina
0.3.1 published 2025-10-03 by GitHub Actions
13 checked, 1 finding(s), 0 not checked
This is a request for review. The two publisher lines show that a maintainer
published by hand, and nothing here says the release is malicious. With
--since origin/main the check covers only what a pull request changes, and
--exclude records a finding that was reviewed. The tool also reports versions
that a lockfile still references but that were removed from the registry.
Testing
The tests run the CLI against a local mock registry with trimmed copies of real metadata. They include the axios history reconstructed as it looked while the malicious version was live. A second suite generates lockfiles with real npm and compares the verdicts with real pnpm. CI runs both on Node 20, 22 and 24 (the live suite on 22).
On a lockfile of 43 widely used packages with 2815 distinct versions, the tool
reported 2 versions and the date rule reported 5. The other three,
@octokit/endpoint@9.0.6, ua-parser-js@1.0.41 and
css-declaration-sorter@6.4.1, are releases on an older line, and pnpm 10.34.9
rejected each of them. The run took 73 seconds. That sample contains no known
compromised version, so it measures how often the check asks for a review and
says nothing about detection rates.
Limits
- Provenance is a signal, and there is a serious argument that it should not be used as a security check at all. A maintainer may stop using it for legitimate reasons, and that is reported too.
- An attacker with access to the CI pipeline publishes with full evidence and is not detected. A package that never had evidence cannot be downgraded.
- The semver condition has a cost. A new release below the first attested version passes, where pnpm would reject it.
- Attestations are not verified cryptographically.
npm audit signaturesdoes that. - It is a separate step, so it does not stop
npm installunless the pipeline runs it first. Only npm lockfiles are read, and requests carry no credentials for private registries.
The code is at github.com/Jwrede/npm-trust-downgrade.