mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-27 18:46:18 -04: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).
|
||||||
@@ -236,6 +236,7 @@ export interface Config extends OptionsFromRootManifest {
|
|||||||
fetchMinSpeedKiBps?: number
|
fetchMinSpeedKiBps?: number
|
||||||
trustPolicy?: TrustPolicy
|
trustPolicy?: TrustPolicy
|
||||||
trustPolicyExclude?: string[]
|
trustPolicyExclude?: string[]
|
||||||
|
trustPolicyIgnoreAfter?: number
|
||||||
|
|
||||||
packageConfigs?: ProjectConfigSet
|
packageConfigs?: ProjectConfigSet
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ export const excludedPnpmKeys = [
|
|||||||
'strict-peer-dependencies',
|
'strict-peer-dependencies',
|
||||||
'trust-policy',
|
'trust-policy',
|
||||||
'trust-policy-exclude',
|
'trust-policy-exclude',
|
||||||
|
'trust-policy-ignore-after',
|
||||||
'use-node-version',
|
'use-node-version',
|
||||||
'use-stderr',
|
'use-stderr',
|
||||||
'verify-deps-before-run',
|
'verify-deps-before-run',
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ export const pnpmTypes = {
|
|||||||
'strict-peer-dependencies': Boolean,
|
'strict-peer-dependencies': Boolean,
|
||||||
'trust-policy': ['off', 'no-downgrade'] satisfies TrustPolicy[],
|
'trust-policy': ['off', 'no-downgrade'] satisfies TrustPolicy[],
|
||||||
'trust-policy-exclude': [String, Array],
|
'trust-policy-exclude': [String, Array],
|
||||||
|
'trust-policy-ignore-after': Number,
|
||||||
'use-beta-cli': Boolean,
|
'use-beta-cli': Boolean,
|
||||||
'use-node-version': String,
|
'use-node-version': String,
|
||||||
'use-running-store-server': Boolean,
|
'use-running-store-server': Boolean,
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ export interface StrictInstallOptions {
|
|||||||
minimumReleaseAgeExclude?: string[]
|
minimumReleaseAgeExclude?: string[]
|
||||||
trustPolicy?: TrustPolicy
|
trustPolicy?: TrustPolicy
|
||||||
trustPolicyExclude?: string[]
|
trustPolicyExclude?: string[]
|
||||||
|
trustPolicyIgnoreAfter?: number
|
||||||
blockExoticSubdeps?: boolean
|
blockExoticSubdeps?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1243,6 +1243,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
|||||||
minimumReleaseAgeExclude: opts.minimumReleaseAgeExclude,
|
minimumReleaseAgeExclude: opts.minimumReleaseAgeExclude,
|
||||||
trustPolicy: opts.trustPolicy,
|
trustPolicy: opts.trustPolicy,
|
||||||
trustPolicyExclude: opts.trustPolicyExclude,
|
trustPolicyExclude: opts.trustPolicyExclude,
|
||||||
|
trustPolicyIgnoreAfter: opts.trustPolicyIgnoreAfter,
|
||||||
blockExoticSubdeps: opts.blockExoticSubdeps,
|
blockExoticSubdeps: opts.blockExoticSubdeps,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ async function resolveAndFetch (
|
|||||||
defaultTag: options.defaultTag,
|
defaultTag: options.defaultTag,
|
||||||
trustPolicy: options.trustPolicy,
|
trustPolicy: options.trustPolicy,
|
||||||
trustPolicyExclude: options.trustPolicyExclude,
|
trustPolicyExclude: options.trustPolicyExclude,
|
||||||
|
trustPolicyIgnoreAfter: options.trustPolicyIgnoreAfter,
|
||||||
publishedBy: options.publishedBy,
|
publishedBy: options.publishedBy,
|
||||||
publishedByExclude: options.publishedByExclude,
|
publishedByExclude: options.publishedByExclude,
|
||||||
pickLowestVersion: options.pickLowestVersion,
|
pickLowestVersion: options.pickLowestVersion,
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export function rcOptionsTypes (): Record<string, unknown> {
|
|||||||
'strict-peer-dependencies',
|
'strict-peer-dependencies',
|
||||||
'trust-policy',
|
'trust-policy',
|
||||||
'trust-policy-exclude',
|
'trust-policy-exclude',
|
||||||
|
'trust-policy-ignore-after',
|
||||||
'unsafe-perm',
|
'unsafe-perm',
|
||||||
'offline',
|
'offline',
|
||||||
'only',
|
'only',
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export function rcOptionsTypes (): Record<string, unknown> {
|
|||||||
'strict-peer-dependencies',
|
'strict-peer-dependencies',
|
||||||
'trust-policy',
|
'trust-policy',
|
||||||
'trust-policy-exclude',
|
'trust-policy-exclude',
|
||||||
|
'trust-policy-ignore-after',
|
||||||
'offline',
|
'offline',
|
||||||
'only',
|
'only',
|
||||||
'optional',
|
'optional',
|
||||||
@@ -212,6 +213,10 @@ by any dependencies, so it is an emulation of a flat node_modules',
|
|||||||
description: 'Exclude specific packages from trust policy checks',
|
description: 'Exclude specific packages from trust policy checks',
|
||||||
name: '--trust-policy-exclude <package-spec>',
|
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`',
|
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',
|
name: '--use-store-server',
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ export interface ResolutionContext {
|
|||||||
publishedByExclude?: PackageVersionPolicy
|
publishedByExclude?: PackageVersionPolicy
|
||||||
trustPolicy?: TrustPolicy
|
trustPolicy?: TrustPolicy
|
||||||
trustPolicyExclude?: PackageVersionPolicy
|
trustPolicyExclude?: PackageVersionPolicy
|
||||||
|
trustPolicyIgnoreAfter?: number
|
||||||
blockExoticSubdeps?: boolean
|
blockExoticSubdeps?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1343,6 +1344,7 @@ async function resolveDependency (
|
|||||||
skipFetch: ctx.dryRun,
|
skipFetch: ctx.dryRun,
|
||||||
trustPolicy: ctx.trustPolicy,
|
trustPolicy: ctx.trustPolicy,
|
||||||
trustPolicyExclude: ctx.trustPolicyExclude,
|
trustPolicyExclude: ctx.trustPolicyExclude,
|
||||||
|
trustPolicyIgnoreAfter: ctx.trustPolicyIgnoreAfter,
|
||||||
update: options.update,
|
update: options.update,
|
||||||
workspacePackages: ctx.workspacePackages,
|
workspacePackages: ctx.workspacePackages,
|
||||||
supportedArchitectures: options.supportedArchitectures,
|
supportedArchitectures: options.supportedArchitectures,
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ export interface ResolveDependenciesOptions {
|
|||||||
minimumReleaseAgeExclude?: string[]
|
minimumReleaseAgeExclude?: string[]
|
||||||
trustPolicy?: TrustPolicy
|
trustPolicy?: TrustPolicy
|
||||||
trustPolicyExclude?: string[]
|
trustPolicyExclude?: string[]
|
||||||
|
trustPolicyIgnoreAfter?: number
|
||||||
blockExoticSubdeps?: boolean
|
blockExoticSubdeps?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +210,7 @@ export async function resolveDependencyTree<T> (
|
|||||||
publishedByExclude: opts.minimumReleaseAgeExclude ? createPackageVersionPolicyByExclude(opts.minimumReleaseAgeExclude, 'minimumReleaseAgeExclude') : undefined,
|
publishedByExclude: opts.minimumReleaseAgeExclude ? createPackageVersionPolicyByExclude(opts.minimumReleaseAgeExclude, 'minimumReleaseAgeExclude') : undefined,
|
||||||
trustPolicy: opts.trustPolicy,
|
trustPolicy: opts.trustPolicy,
|
||||||
trustPolicyExclude: opts.trustPolicyExclude ? createPackageVersionPolicyByExclude(opts.trustPolicyExclude, 'trustPolicyExclude') : undefined,
|
trustPolicyExclude: opts.trustPolicyExclude ? createPackageVersionPolicyByExclude(opts.trustPolicyExclude, 'trustPolicyExclude') : undefined,
|
||||||
|
trustPolicyIgnoreAfter: opts.trustPolicyIgnoreAfter,
|
||||||
blockExoticSubdeps: opts.blockExoticSubdeps,
|
blockExoticSubdeps: opts.blockExoticSubdeps,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -563,3 +563,15 @@ test('install fails when trust evidence of an optional dependency is downgraded'
|
|||||||
expect(result.stdout.toString()).toContain('ERR_PNPM_TRUST_DOWNGRADE')
|
expect(result.stdout.toString()).toContain('ERR_PNPM_TRUST_DOWNGRADE')
|
||||||
expect(result.status).toBe(1)
|
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
|
pickLowestVersion?: boolean
|
||||||
trustPolicy?: TrustPolicy
|
trustPolicy?: TrustPolicy
|
||||||
trustPolicyExclude?: PackageVersionPolicy
|
trustPolicyExclude?: PackageVersionPolicy
|
||||||
|
trustPolicyIgnoreAfter?: number
|
||||||
dryRun?: boolean
|
dryRun?: boolean
|
||||||
lockfileDir?: string
|
lockfileDir?: string
|
||||||
preferredVersions?: PreferredVersions
|
preferredVersions?: PreferredVersions
|
||||||
@@ -333,7 +334,7 @@ async function resolveNpm (
|
|||||||
}
|
}
|
||||||
throw new NoMatchingVersionError({ wantedDependency, packageMeta: meta, registry })
|
throw new NoMatchingVersionError({ wantedDependency, packageMeta: meta, registry })
|
||||||
} else if (opts.trustPolicy === 'no-downgrade') {
|
} else if (opts.trustPolicy === 'no-downgrade') {
|
||||||
failIfTrustDowngraded(meta, pickedPackage.version, opts.trustPolicyExclude)
|
failIfTrustDowngraded(meta, pickedPackage.version, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
const workspacePkgsMatchingName = workspacePackages?.get(pickedPackage.name)
|
const workspacePkgsMatchingName = workspacePackages?.get(pickedPackage.name)
|
||||||
|
|||||||
@@ -14,10 +14,13 @@ const TRUST_RANK = {
|
|||||||
export function failIfTrustDowngraded (
|
export function failIfTrustDowngraded (
|
||||||
meta: PackageMeta,
|
meta: PackageMeta,
|
||||||
version: string,
|
version: string,
|
||||||
trustPolicyExclude?: PackageVersionPolicy
|
opts?: {
|
||||||
|
trustPolicyExclude?: PackageVersionPolicy
|
||||||
|
trustPolicyIgnoreAfter?: number
|
||||||
|
}
|
||||||
): void {
|
): void {
|
||||||
if (trustPolicyExclude) {
|
if (opts?.trustPolicyExclude) {
|
||||||
const excludeResult = trustPolicyExclude(meta.name)
|
const excludeResult = opts.trustPolicyExclude(meta.name)
|
||||||
if (excludeResult === true) {
|
if (excludeResult === true) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -37,6 +40,13 @@ export function failIfTrustDowngraded (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const versionDate = new Date(versionPublishedAt)
|
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]
|
const manifest = meta.versions[version]
|
||||||
if (!manifest) {
|
if (!manifest) {
|
||||||
throw new PnpmError(
|
throw new PnpmError(
|
||||||
|
|||||||
@@ -488,7 +488,7 @@ describe('failIfTrustDowngraded with trustPolicyExclude', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
failIfTrustDowngraded(meta, '3.0.0', createPackageVersionPolicy(['foo@3.0.0']))
|
failIfTrustDowngraded(meta, '3.0.0', { trustPolicyExclude: createPackageVersionPolicy(['foo@3.0.0']) })
|
||||||
}).not.toThrow()
|
}).not.toThrow()
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
@@ -533,7 +533,7 @@ describe('failIfTrustDowngraded with trustPolicyExclude', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
failIfTrustDowngraded(meta, '3.0.0', createPackageVersionPolicy(['bar']))
|
failIfTrustDowngraded(meta, '3.0.0', { trustPolicyExclude: createPackageVersionPolicy(['bar']) })
|
||||||
}).not.toThrow()
|
}).not.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -555,7 +555,7 @@ describe('failIfTrustDowngraded with trustPolicyExclude', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
failIfTrustDowngraded(meta, '1.0.0', createPackageVersionPolicy(['baz@1.0.0']))
|
failIfTrustDowngraded(meta, '1.0.0', { trustPolicyExclude: createPackageVersionPolicy(['baz@1.0.0']) })
|
||||||
}).not.toThrow()
|
}).not.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -585,7 +585,51 @@ describe('failIfTrustDowngraded with trustPolicyExclude', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
failIfTrustDowngraded(meta, '2.0.0', createPackageVersionPolicy(['qux']))
|
failIfTrustDowngraded(meta, '2.0.0', { trustPolicyExclude: createPackageVersionPolicy(['qux']) })
|
||||||
}).not.toThrow()
|
}).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')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ export interface ResolveOptions {
|
|||||||
alwaysTryWorkspacePackages?: boolean
|
alwaysTryWorkspacePackages?: boolean
|
||||||
trustPolicy?: TrustPolicy
|
trustPolicy?: TrustPolicy
|
||||||
trustPolicyExclude?: PackageVersionPolicy
|
trustPolicyExclude?: PackageVersionPolicy
|
||||||
|
trustPolicyIgnoreAfter?: number
|
||||||
defaultTag?: string
|
defaultTag?: string
|
||||||
pickLowestVersion?: boolean
|
pickLowestVersion?: boolean
|
||||||
publishedBy?: Date
|
publishedBy?: Date
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ export interface RequestPackageOptions {
|
|||||||
pinnedVersion?: PinnedVersion
|
pinnedVersion?: PinnedVersion
|
||||||
trustPolicy?: TrustPolicy
|
trustPolicy?: TrustPolicy
|
||||||
trustPolicyExclude?: PackageVersionPolicy
|
trustPolicyExclude?: PackageVersionPolicy
|
||||||
|
trustPolicyIgnoreAfter?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BundledManifestFunction = () => Promise<BundledManifest | undefined>
|
export type BundledManifestFunction = () => Promise<BundledManifest | undefined>
|
||||||
|
|||||||
Reference in New Issue
Block a user