Skip to content

feat: add no-downgrade package trust policy - #9902

Open
isaacsamual994-lang wants to merge 36 commits into
npm:latestfrom
isaacsamual994-lang:feat/trust-policy-no-downgrade
Open

feat: add no-downgrade package trust policy#9902
isaacsamual994-lang wants to merge 36 commits into
npm:latestfrom
isaacsamual994-lang:feat/trust-policy-no-downgrade

Conversation

@isaacsamual994-lang

@isaacsamual994-lang isaacsamual994-lang commented Aug 20, 2026

Copy link
Copy Markdown

What / Why

Adds opt-in trust-policy=no-downgrade enforcement for registry dependencies, addressing #9242.

When enabled, npm rejects a selected package version when an earlier-published version in the same major release line established stronger trust evidence:

trusted publisher provenance > provenance attestation > no trust evidence

The policy runs before reification for npm install, npm update, and npm ci, so a detected downgrade is blocked before files are written to node_modules.

Configuration

trust-policy=no-downgrade
trust-policy-exclude[]=chokidar@4.0.3
trust-policy-exclude[]=webpack@4.47.0 || 5.102.1
trust-policy-ignore-after=525600

Security model

This detects a credential-theft pattern where an attacker can publish with a stolen npm token but cannot reproduce the project's trusted CI/provenance path. A version that drops from trusted-publisher or provenance evidence to weaker or absent evidence is rejected.

To avoid comparing independent maintenance lines, trust history is evaluated only within the selected version's semver major release line. Publish chronology is still used to determine which versions are earlier within that line. For example, a later v6 maintenance release is not judged against trust evidence first introduced on v7.

The implementation:

  • fetches one full registry packument per package and checks all selected versions;
  • uses publish chronology within the same semver major release line;
  • keeps stable releases isolated from historical prerelease evidence;
  • handles npm aliases using the underlying package name;
  • skips workspaces, links, bundled children, and clearly non-registry git/file/remote dependencies;
  • deduplicates duplicate package versions;
  • preserves scoped and multiple-registry configuration;
  • checks locked dependencies and npm ci installs;
  • fails closed when selected-version metadata required for enforcement is missing;
  • reports ETRUSTDOWNGRADE or ETRUSTPOLICYMETADATA, with actionable exclusion remediation.

A key npm ci detail is that Arborist may receive policy settings in constructor options and call buildIdealTree() without method arguments. The preflight therefore verifies using Arborist's effective options, with regression coverage.

Testing

Focused coverage includes:

  • trust ordering, same-major release-line handling, publish chronology, prereleases, exclusions, and ignore-after;
  • registry/non-registry filtering, aliases, bundles, deduplication, and scoped registries;
  • real Arborist registry resolution;
  • locked dependencies and CI-style constructor options;
  • config defaults and flattening;
  • install/update/ci preflight integration.

Validation performed:

node node_modules/tap/bin/run.js --no-coverage \
  test/lib/utils/trust-policy-preflight.js \
  workspaces/arborist/test/trust-policy.js \
  workspaces/arborist/test/trust-policy-verifier.js \
  workspaces/arborist/test/arborist/trust-policy.js \
  workspaces/config/test/definitions/index.js

npx eslint <all touched JavaScript files>
git diff --check origin/latest...HEAD

All focused tests passed, touched-file lint passed, and the final fork tree was verified byte-for-byte against the tested local tree.

Addresses #9242

References

@isaacsamual994-lang
isaacsamual994-lang requested review from a team as code owners August 20, 2026 13:02
@isaacsamual994-lang isaacsamual994-lang changed the title feat: add no-downgrade package trust policyFeat/trust policy no downgrade feat: add no-downgrade package trust policyFeat/trust policy no downgradefeat: add no-downgrade package trust policy Aug 20, 2026
@isaacsamual994-lang isaacsamual994-lang changed the title feat: add no-downgrade package trust policyFeat/trust policy no downgradefeat: add no-downgrade package trust policy feat: add no-downgrade package trust policyFeat/trust policy no downgradefeat: add no-downgrade package trust policy Aug 20, 2026
@isaacsamual994-lang

Copy link
Copy Markdown
Author

Additional validation against npm's current latest completed successfully:

  • focused trust-policy, config, and real-Arborist tests passed with exit 0;
  • the full install, ci, and update command test files passed with exit 0 using --no-coverage;
  • ESLint passed across every touched JavaScript file;
  • git diff --check passed.

The clean local review commit was also reconstructed from current upstream latest and produced the same 13-file patch.

@isaacsamual994-lang isaacsamual994-lang changed the title feat: add no-downgrade package trust policyFeat/trust policy no downgradefeat: add no-downgrade package trust policy feat: add no-downgrade package trust policy Aug 20, 2026
@ljharb

ljharb commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

This is a terrible idea. First of all, provenance is NOT a security indicator. Second, it's perfectly legitimate for a maintainer to decide to start using provenance, and then decide to stop. Third, pnpm's implementation of it has (or at least, had, I'm not sure) a bug (semver v7 is published with provenance, v6 is not, but pnpm was keying on dates, not release lines, to mark a "downgrade"), and that highlights some of the flaws in even attempting to use provenance in this manner (this PR seems to do the same).

Provenance's only value whatsoever is post-incident forensics, and it should never be used as part of an install gate of any kind. This should simply be closed.

@isaacsamual994-lang

Copy link
Copy Markdown
Author

Thanks for calling out the cross-release-line case. I agree that comparing a maintenance release in one major line against trust evidence introduced on another major line would create false positives.
I’ve updated the implementation so trust history is now evaluated only within the selected version’s semver major release line, while still using publish chronology inside that line. I also added regression coverage for the v6/v7-style case you described and adjusted the integration test to keep the downgrade comparison within a single major.
Separately, this remains opt-in. The intent is not to treat provenance as proof that package contents are safe; it is to let consumers who choose this policy enforce continuity of a publication signal they rely on, with explicit exclusions available for expected changes in publishing practice.
Happy to adjust the model further if the maintainers prefer a narrower definition of the release line or a different opt-in shape.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants