You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(npm): publishing-trust ranking and no-downgrade trust policy (#34927)
This teaches Deno's npm resolver about how a package version was
published and adds an opt-in `no-downgrade` trust policy, following
pnpm's implementation (`resolving/npm-resolver/src/trustChecks.ts`)
closely.
npm's full packument exposes whether a version was published via trusted
publishing (OIDC, `_npmUser.trustedPublisher`), carries a provenance
attestation (`dist.attestations.provenance`), or went through staged
publishing (`_npmUser.approver`, a maintainer approving with a live 2FA
challenge). Each version's strongest signal is ranked into a single
trust level. The tiers are mutually exclusive and mirror pnpm: a staged
publish is strongest, then trusted publishing *backed by* a provenance
attestation, then a provenance attestation on its own. A
`trustedPublisher` flag without provenance is deliberately not counted,
since on its own it is just metadata a future staged-publish flow could
mint.
The `no-downgrade` policy, enabled with `trust-policy=no-downgrade` in
`.npmrc`, refuses to resolve a version whose trust evidence is weaker
than the strongest evidence on any earlier-published version of the same
package. The comparison is by publish date, not semver, exactly as
pnpm does it: if a package has been published through trusted publishing
or with provenance and a later version suddenly appears as a plain token
publish, that is a strong signal of a stolen maintainer token, and the
policy turns it into a hard error instead of a silent install. When a
downgrade is the only thing blocking resolution, the resolver returns a
dedicated error explaining the rejection and how to override it, rather
than silently falling back to an older version.
The motivation is supply-chain safety. This is the same defense that
would have caught the August 2025 s1ngularity incident, where a package
consistently published through CI was suddenly published from a laptop
with basic credentials.
The trust signals only exist in the full packument. Enabling the policy
fetches the full packument, but `min-release-age` already makes Deno do
that by default, so in the common case there is no extra fetch. To keep
the cached `registry.json` small, the never-read signal objects
(`_npmUser.approver`, `_npmUser.trustedPublisher`,
`dist.attestations.provenance`) are reduced to a one-byte presence
marker before the packument is cached.
A `trust-policy-ignore-after` setting (in minutes, mirroring pnpm's
`trustPolicyIgnoreAfter`) skips the check for versions published more
than that long ago, so genuinely pre-provenance releases still install.
It is turned into an absolute cutoff in the resolver factory so the
resolver stays free of a wall clock.
Coverage: tiered ranking, the publish-date downgrade scan (including
prerelease exclusion and the ignore-after cutoff), the targeted
downgrade error, and compact cache serialization are covered by resolver
unit tests. An end-to-end spec test installs a package whose newer
version downgrades from a staged publish to a plain publish and asserts
the install is rejected, plus a spec test that the cache keeps compact
markers. The test npm registry now preserves `dist.attestations` from
fixtures so provenance is exercisable end to end.
Unlike pnpm, the trust level is not persisted to the lockfile: the
policy recomputes the baseline from the packument's version history on
each resolution, so existing lockfiles are unaffected.
A `trust-policy-exclude[]` setting (mirroring pnpm's
`trustPolicyExclude`) exempts named packages from the policy: repeated
`trust-policy-exclude[]=<package>` entries in `.npmrc` list packages
that resolve as if the policy were off. Home and project excludes are
unioned.
The policy is opt-in and stays off by default. Provenance, trusted
publishing and staged publishing are still unevenly adopted across the
registry, so enabling `no-downgrade` by default would turn legitimate
trust-evidence gaps into hard install failures; it can be revisited
once adoption is high.
0 commit comments