mirror of
https://github.com/pnpm/pnpm.git
synced 2026-01-15 02:18:31 -05:00
feat: add trustPolicyIgnoreAfter (#10359)
* feat: add `trustPolicyIgnoreAfter` * Update .changeset/big-lies-pump.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * refactor: npm-resolver --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
13
.changeset/big-lies-pump.md
Normal file
13
.changeset/big-lies-pump.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-installation": minor
|
||||
"@pnpm/resolve-dependencies": minor
|
||||
"@pnpm/package-requester": minor
|
||||
"@pnpm/store-controller-types": minor
|
||||
"@pnpm/resolver-base": minor
|
||||
"@pnpm/npm-resolver": minor
|
||||
"@pnpm/core": minor
|
||||
"@pnpm/config": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
Adding `trustPolicyIgnoreAfter` allows you to ignore trust policy checks for packages published more than a specified time ago[#10352](https://github.com/pnpm/pnpm/issues/10352).
|
||||
@@ -237,6 +237,7 @@ export interface Config extends OptionsFromRootManifest {
|
||||
fetchMinSpeedKiBps?: number
|
||||
trustPolicy?: TrustPolicy
|
||||
trustPolicyExclude?: string[]
|
||||
trustPolicyIgnoreAfter?: number
|
||||
}
|
||||
|
||||
export interface ConfigWithDeprecatedSettings extends Config {
|
||||
|
||||
@@ -117,6 +117,7 @@ export const types = Object.assign({
|
||||
'strict-peer-dependencies': Boolean,
|
||||
'trust-policy': ['off', 'no-downgrade'] satisfies TrustPolicy[],
|
||||
'trust-policy-exclude': [String, Array],
|
||||
'trust-policy-ignore-after': Number,
|
||||
'use-beta-cli': Boolean,
|
||||
'use-node-version': String,
|
||||
'use-running-store-server': Boolean,
|
||||
|
||||
@@ -170,6 +170,7 @@ export interface StrictInstallOptions {
|
||||
minimumReleaseAgeExclude?: string[]
|
||||
trustPolicy?: TrustPolicy
|
||||
trustPolicyExclude?: string[]
|
||||
trustPolicyIgnoreAfter?: number
|
||||
blockExoticSubdeps?: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -1227,6 +1227,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
minimumReleaseAgeExclude: opts.minimumReleaseAgeExclude,
|
||||
trustPolicy: opts.trustPolicy,
|
||||
trustPolicyExclude: opts.trustPolicyExclude,
|
||||
trustPolicyIgnoreAfter: opts.trustPolicyIgnoreAfter,
|
||||
blockExoticSubdeps: opts.blockExoticSubdeps,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -207,6 +207,7 @@ async function resolveAndFetch (
|
||||
defaultTag: options.defaultTag,
|
||||
trustPolicy: options.trustPolicy,
|
||||
trustPolicyExclude: options.trustPolicyExclude,
|
||||
trustPolicyIgnoreAfter: options.trustPolicyIgnoreAfter,
|
||||
publishedBy: options.publishedBy,
|
||||
publishedByExclude: options.publishedByExclude,
|
||||
pickLowestVersion: options.pickLowestVersion,
|
||||
|
||||
@@ -76,6 +76,7 @@ export function rcOptionsTypes (): Record<string, unknown> {
|
||||
'strict-peer-dependencies',
|
||||
'trust-policy',
|
||||
'trust-policy-exclude',
|
||||
'trust-policy-ignore-after',
|
||||
'unsafe-perm',
|
||||
'offline',
|
||||
'only',
|
||||
|
||||
@@ -65,6 +65,7 @@ export function rcOptionsTypes (): Record<string, unknown> {
|
||||
'strict-peer-dependencies',
|
||||
'trust-policy',
|
||||
'trust-policy-exclude',
|
||||
'trust-policy-ignore-after',
|
||||
'offline',
|
||||
'only',
|
||||
'optional',
|
||||
@@ -213,6 +214,10 @@ by any dependencies, so it is an emulation of a flat node_modules',
|
||||
description: 'Exclude specific packages from trust policy checks',
|
||||
name: '--trust-policy-exclude <package-spec>',
|
||||
},
|
||||
{
|
||||
description: 'Ignore trust downgrades for packages published more than specified minutes ago',
|
||||
name: '--trust-policy-ignore-after <minutes>',
|
||||
},
|
||||
{
|
||||
description: 'Starts a store server in the background. The store server will keep running after installation is done. To stop the store server, run `pnpm server stop`',
|
||||
name: '--use-store-server',
|
||||
|
||||
@@ -187,6 +187,7 @@ export interface ResolutionContext {
|
||||
publishedByExclude?: PackageVersionPolicy
|
||||
trustPolicy?: TrustPolicy
|
||||
trustPolicyExclude?: PackageVersionPolicy
|
||||
trustPolicyIgnoreAfter?: number
|
||||
blockExoticSubdeps?: boolean
|
||||
}
|
||||
|
||||
@@ -1345,6 +1346,7 @@ async function resolveDependency (
|
||||
skipFetch: ctx.dryRun,
|
||||
trustPolicy: ctx.trustPolicy,
|
||||
trustPolicyExclude: ctx.trustPolicyExclude,
|
||||
trustPolicyIgnoreAfter: ctx.trustPolicyIgnoreAfter,
|
||||
update: options.update,
|
||||
workspacePackages: ctx.workspacePackages,
|
||||
supportedArchitectures: options.supportedArchitectures,
|
||||
|
||||
@@ -143,6 +143,7 @@ export interface ResolveDependenciesOptions {
|
||||
minimumReleaseAgeExclude?: string[]
|
||||
trustPolicy?: TrustPolicy
|
||||
trustPolicyExclude?: string[]
|
||||
trustPolicyIgnoreAfter?: number
|
||||
blockExoticSubdeps?: boolean
|
||||
}
|
||||
|
||||
@@ -209,6 +210,7 @@ export async function resolveDependencyTree<T> (
|
||||
publishedByExclude: opts.minimumReleaseAgeExclude ? createPackageVersionPolicyByExclude(opts.minimumReleaseAgeExclude, 'minimumReleaseAgeExclude') : undefined,
|
||||
trustPolicy: opts.trustPolicy,
|
||||
trustPolicyExclude: opts.trustPolicyExclude ? createPackageVersionPolicyByExclude(opts.trustPolicyExclude, 'trustPolicyExclude') : undefined,
|
||||
trustPolicyIgnoreAfter: opts.trustPolicyIgnoreAfter,
|
||||
blockExoticSubdeps: opts.blockExoticSubdeps,
|
||||
}
|
||||
|
||||
|
||||
@@ -559,3 +559,15 @@ test('install fails when trust evidence of an optional dependency is downgraded'
|
||||
expect(result.stdout.toString()).toContain('ERR_PNPM_TRUST_DOWNGRADE')
|
||||
expect(result.status).toBe(1)
|
||||
})
|
||||
|
||||
test('install does not fail when the trust evidence of a package is downgraded but the trust-policy-ignore-after is set', async () => {
|
||||
const project = prepare()
|
||||
const result = execPnpmSync([
|
||||
'add',
|
||||
'@pnpm/e2e.test-provenance@0.0.5',
|
||||
'--trust-policy=no-downgrade',
|
||||
'--trust-policy-ignore-after=1440', // 1 day
|
||||
])
|
||||
expect(result.status).toBe(0)
|
||||
project.has('@pnpm/e2e.test-provenance')
|
||||
})
|
||||
|
||||
@@ -214,6 +214,7 @@ export type ResolveFromNpmOptions = {
|
||||
pickLowestVersion?: boolean
|
||||
trustPolicy?: TrustPolicy
|
||||
trustPolicyExclude?: PackageVersionPolicy
|
||||
trustPolicyIgnoreAfter?: number
|
||||
dryRun?: boolean
|
||||
lockfileDir?: string
|
||||
preferredVersions?: PreferredVersions
|
||||
@@ -333,7 +334,7 @@ async function resolveNpm (
|
||||
}
|
||||
throw new NoMatchingVersionError({ wantedDependency, packageMeta: meta, registry })
|
||||
} else if (opts.trustPolicy === 'no-downgrade') {
|
||||
failIfTrustDowngraded(meta, pickedPackage.version, opts.trustPolicyExclude)
|
||||
failIfTrustDowngraded(meta, pickedPackage.version, opts)
|
||||
}
|
||||
|
||||
const workspacePkgsMatchingName = workspacePackages?.get(pickedPackage.name)
|
||||
|
||||
@@ -14,10 +14,13 @@ const TRUST_RANK = {
|
||||
export function failIfTrustDowngraded (
|
||||
meta: PackageMeta,
|
||||
version: string,
|
||||
trustPolicyExclude?: PackageVersionPolicy
|
||||
opts?: {
|
||||
trustPolicyExclude?: PackageVersionPolicy
|
||||
trustPolicyIgnoreAfter?: number
|
||||
}
|
||||
): void {
|
||||
if (trustPolicyExclude) {
|
||||
const excludeResult = trustPolicyExclude(meta.name)
|
||||
if (opts?.trustPolicyExclude) {
|
||||
const excludeResult = opts.trustPolicyExclude(meta.name)
|
||||
if (excludeResult === true) {
|
||||
return
|
||||
}
|
||||
@@ -37,6 +40,13 @@ export function failIfTrustDowngraded (
|
||||
}
|
||||
|
||||
const versionDate = new Date(versionPublishedAt)
|
||||
if (opts?.trustPolicyIgnoreAfter) {
|
||||
const now = new Date()
|
||||
const minutesSincePublish = (now.getTime() - versionDate.getTime()) / (1000 * 60)
|
||||
if (minutesSincePublish > opts.trustPolicyIgnoreAfter) {
|
||||
return
|
||||
}
|
||||
}
|
||||
const manifest = meta.versions[version]
|
||||
if (!manifest) {
|
||||
throw new PnpmError(
|
||||
|
||||
@@ -488,7 +488,7 @@ describe('failIfTrustDowngraded with trustPolicyExclude', () => {
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
failIfTrustDowngraded(meta, '3.0.0', createPackageVersionPolicy(['foo@3.0.0']))
|
||||
failIfTrustDowngraded(meta, '3.0.0', { trustPolicyExclude: createPackageVersionPolicy(['foo@3.0.0']) })
|
||||
}).not.toThrow()
|
||||
|
||||
expect(() => {
|
||||
@@ -533,7 +533,7 @@ describe('failIfTrustDowngraded with trustPolicyExclude', () => {
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
failIfTrustDowngraded(meta, '3.0.0', createPackageVersionPolicy(['bar']))
|
||||
failIfTrustDowngraded(meta, '3.0.0', { trustPolicyExclude: createPackageVersionPolicy(['bar']) })
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
@@ -555,7 +555,7 @@ describe('failIfTrustDowngraded with trustPolicyExclude', () => {
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
failIfTrustDowngraded(meta, '1.0.0', createPackageVersionPolicy(['baz@1.0.0']))
|
||||
failIfTrustDowngraded(meta, '1.0.0', { trustPolicyExclude: createPackageVersionPolicy(['baz@1.0.0']) })
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
@@ -585,7 +585,51 @@ describe('failIfTrustDowngraded with trustPolicyExclude', () => {
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
failIfTrustDowngraded(meta, '2.0.0', createPackageVersionPolicy(['qux']))
|
||||
failIfTrustDowngraded(meta, '2.0.0', { trustPolicyExclude: createPackageVersionPolicy(['qux']) })
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('failIfTrustDowngraded with trustPolicyIgnoreAfter', () => {
|
||||
test('allows downgrade when version is older than ignoreAfter threshold', () => {
|
||||
const meta: PackageMetaWithTime = {
|
||||
name: 'foo',
|
||||
'dist-tags': { latest: '3.0.0' },
|
||||
versions: {
|
||||
'2.0.0': {
|
||||
name: 'foo',
|
||||
version: '2.0.0',
|
||||
dist: {
|
||||
shasum: 'def456',
|
||||
tarball: 'https://registry.example.com/foo/-/foo-2.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: {
|
||||
'2.0.0': '2025-02-01T00:00:00.000Z',
|
||||
'3.0.0': '2025-03-01T00:00:00.000Z',
|
||||
},
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
failIfTrustDowngraded(meta, '3.0.0', { trustPolicyIgnoreAfter: 60 * 24 * 30 }) // 30 days
|
||||
}).not.toThrow()
|
||||
|
||||
expect(() => {
|
||||
failIfTrustDowngraded(meta, '3.0.0')
|
||||
}).toThrow('High-risk trust downgrade')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -110,6 +110,7 @@ export interface ResolveOptions {
|
||||
alwaysTryWorkspacePackages?: boolean
|
||||
trustPolicy?: TrustPolicy
|
||||
trustPolicyExclude?: PackageVersionPolicy
|
||||
trustPolicyIgnoreAfter?: number
|
||||
defaultTag?: string
|
||||
pickLowestVersion?: boolean
|
||||
publishedBy?: Date
|
||||
|
||||
@@ -143,6 +143,7 @@ export interface RequestPackageOptions {
|
||||
pinnedVersion?: PinnedVersion
|
||||
trustPolicy?: TrustPolicy
|
||||
trustPolicyExclude?: PackageVersionPolicy
|
||||
trustPolicyIgnoreAfter?: number
|
||||
}
|
||||
|
||||
export type BundledManifestFunction = () => Promise<BundledManifest | undefined>
|
||||
|
||||
Reference in New Issue
Block a user