diff --git a/.changeset/many-showers-beg.md b/.changeset/many-showers-beg.md new file mode 100644 index 0000000000..314edb153c --- /dev/null +++ b/.changeset/many-showers-beg.md @@ -0,0 +1,6 @@ +--- +"@pnpm/npm-resolver": patch +"pnpm": patch +--- + +`trustPolicy` should ignore the trust evidences of prerelease versions, when installing a non-prerelease version. diff --git a/resolving/npm-resolver/src/trustChecks.ts b/resolving/npm-resolver/src/trustChecks.ts index 9416e409c7..c01ce99626 100644 --- a/resolving/npm-resolver/src/trustChecks.ts +++ b/resolving/npm-resolver/src/trustChecks.ts @@ -1,6 +1,7 @@ import { PnpmError } from '@pnpm/error' import { type PackageInRegistry, type PackageMetaWithTime } from '@pnpm/registry.types' import { type PackageVersionPolicy } from '@pnpm/types' +import semver from 'semver' type TrustEvidence = 'provenance' | 'trustedPublisher' @@ -41,7 +42,9 @@ export function failIfTrustDowngraded ( ) } - const strongestEvidencePriorToRequestedVersion = detectStrongestTrustEvidenceBeforeDate(meta, versionDate) + const strongestEvidencePriorToRequestedVersion = detectStrongestTrustEvidenceBeforeDate(meta, versionDate, { + excludePrerelease: !semver.prerelease(version, true), + }) if (strongestEvidencePriorToRequestedVersion == null) { return } @@ -72,11 +75,15 @@ function prettyPrintTrustEvidence (trustEvidence: TrustEvidence | undefined): st function detectStrongestTrustEvidenceBeforeDate ( meta: PackageMetaWithTime, - beforeDate: Date + beforeDate: Date, + options: { + excludePrerelease: boolean + } ): TrustEvidence | undefined { let best: TrustEvidence | undefined for (const [version, manifest] of Object.entries(meta.versions)) { + if (options.excludePrerelease && semver.prerelease(version, true)) continue const ts = meta.time[version] if (!ts) continue diff --git a/resolving/npm-resolver/test/trustChecks.test.ts b/resolving/npm-resolver/test/trustChecks.test.ts index 4bd62817d1..555c65ee0e 100644 --- a/resolving/npm-resolver/test/trustChecks.test.ts +++ b/resolving/npm-resolver/test/trustChecks.test.ts @@ -210,6 +210,52 @@ describe('failIfTrustDowngraded', () => { }).toThrow('High-risk trust downgrade') }) + test('does not throw an error when only prerelease versions had provenance', () => { + const meta: PackageMetaWithTime = { + name: 'foo', + 'dist-tags': { latest: '3.0.0' }, + versions: { + '1.0.0': { + name: 'foo', + version: '1.0.0', + dist: { + shasum: 'abc123', + tarball: 'https://registry.example.com/foo/-/foo-1.0.0.tgz', + }, + }, + '2.0.0-0': { + name: 'foo', + version: '2.0.0-0', + dist: { + shasum: 'def456', + tarball: 'https://registry.example.com/foo/-/foo-2.0.0-0.tgz', + attestations: { + provenance: { + predicateType: 'https://slsa.dev/provenance/v1', + }, + }, + }, + }, + '3.0.0': { + name: 'foo', + version: '3.0.0', + dist: { + shasum: 'ghi789', + tarball: 'https://registry.example.com/foo/-/foo-3.0.0.tgz', + }, + }, + }, + time: { + '1.0.0': '2025-01-01T00:00:00.000Z', + '2.0.0-0': '2025-02-01T00:00:00.000Z', + '3.0.0': '2025-03-01T00:00:00.000Z', + }, + } + expect(() => { + failIfTrustDowngraded(meta, '3.0.0') + }).not.toThrow() + }) + test('throws an error when downgrading from trustedPublisher to provenance', () => { const meta: PackageMetaWithTime = { name: 'foo',