From 190c2a0c34dbf344a3e32b694b9462cc916f0c6f Mon Sep 17 00:00:00 2001 From: jdalton Date: Tue, 1 Jul 2025 13:29:16 -0400 Subject: [PATCH] Add --min-satisfying option to fix command --- src/commands/fix/agent-fix.mts | 20 ++++++++++++++------ src/commands/fix/cmd-fix.mts | 15 +++++++++++++++ src/commands/fix/cmd-fix.test.mts | 1 + src/commands/fix/handle-fix.mts | 2 ++ src/shadow/npm/arborist-helpers.mts | 19 +++++++++++++++++-- 5 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/commands/fix/agent-fix.mts b/src/commands/fix/agent-fix.mts index e1f71d577..3ca838476 100644 --- a/src/commands/fix/agent-fix.mts +++ b/src/commands/fix/agent-fix.mts @@ -64,6 +64,7 @@ export type FixConfig = { autoMerge: boolean cwd: string limit: number + minSatisfying: boolean purls: string[] rangeStyle: RangeStyle spinner: Spinner | undefined @@ -113,8 +114,16 @@ export async function agentFix( fixConfig: FixConfig, ): Promise> { const { pkgPath: rootPath } = pkgEnvDetails - const { autoMerge, cwd, limit, rangeStyle, spinner, test, testScript } = - fixConfig + const { + autoMerge, + cwd, + limit, + minSatisfying, + rangeStyle, + spinner, + test, + testScript, + } = fixConfig let count = 0 @@ -292,11 +301,10 @@ export async function agentFix( firstPatchedVersionIdentifier, vulnerableVersionRange, } of infos) { - const newVersion = findBestPatchVersion( - node, - availableVersions, + const newVersion = findBestPatchVersion(node, availableVersions, { + minSatisfying, vulnerableVersionRange, - ) + }) const newVersionPackument = newVersion ? packument.versions[newVersion] : undefined diff --git a/src/commands/fix/cmd-fix.mts b/src/commands/fix/cmd-fix.mts index 7a84b58af..a4a136491 100644 --- a/src/commands/fix/cmd-fix.mts +++ b/src/commands/fix/cmd-fix.mts @@ -53,6 +53,18 @@ const config: CliCommandConfig = { default: Infinity, description: 'The number of fixes to attempt at a time', }, + maxSatisfying: { + type: 'boolean', + default: true, + description: 'Use the maximum satisfying version for dependency updates', + hidden: true, + }, + minSatisfying: { + type: 'boolean', + default: false, + description: + 'Constrain dependency updates to the minimum satisfying version', + }, purl: { type: 'string', default: [], @@ -170,6 +182,8 @@ async function run( (cli.flags['limit'] ? parseInt(String(cli.flags['limit'] || ''), 10) : Infinity) || Infinity + const maxSatisfying = Boolean(cli.flags['maxSatisfying']) + const minSatisfying = Boolean(cli.flags['minSatisfying']) || !maxSatisfying const purls = cmdFlagValueToArray(cli.flags['purl']) const testScript = String(cli.flags['testScript'] || 'test') @@ -178,6 +192,7 @@ async function run( cwd, ghsas, limit, + minSatisfying, outputKind, purls, rangeStyle, diff --git a/src/commands/fix/cmd-fix.test.mts b/src/commands/fix/cmd-fix.test.mts index 19b2ec666..7fbe841e8 100644 --- a/src/commands/fix/cmd-fix.test.mts +++ b/src/commands/fix/cmd-fix.test.mts @@ -26,6 +26,7 @@ describe('socket fix', async () => { --ghsa Provide a list of GHSA IDs (\\u200bhttps://docs.github.com/en/code-security/security-advisories/working-with-global-security-advisories-from-the-github-advisory-database/about-the-github-advisory-database#about-ghsa-ids\\u200b) to compute fixes for, as either a comma separated value or as multiple flags. Use '--ghsa auto' to automatically lookup GHSA IDs and compute fixes for them. --limit The number of fixes to attempt at a time + --minSatisfying Constrain dependency updates to the minimum satisfying version --purl Provide a list of PURLs (\\u200bhttps://github.com/package-url/purl-spec?tab=readme-ov-file#purl\\u200b) to compute fixes for, as either a comma separated value or as multiple flags, instead of querying the Socket API --rangeStyle Define how updated dependency versions should be written in package.json. diff --git a/src/commands/fix/handle-fix.mts b/src/commands/fix/handle-fix.mts index 7fa779077..af2bc4599 100644 --- a/src/commands/fix/handle-fix.mts +++ b/src/commands/fix/handle-fix.mts @@ -30,6 +30,7 @@ export async function handleFix({ cwd, ghsas, limit, + minSatisfying, outputKind, purls, rangeStyle, @@ -151,6 +152,7 @@ export async function handleFix({ autoMerge, cwd, limit, + minSatisfying, purls, rangeStyle, spinner, diff --git a/src/shadow/npm/arborist-helpers.mts b/src/shadow/npm/arborist-helpers.mts index 1fb45cbd2..47d375016 100644 --- a/src/shadow/npm/arborist-helpers.mts +++ b/src/shadow/npm/arborist-helpers.mts @@ -41,11 +41,20 @@ function getUrlOrigin(input: string): string { return '' } +export type BestPatchVersionOptions = { + minSatisfying?: boolean | undefined + vulnerableVersionRange?: string | undefined +} + export function findBestPatchVersion( node: NodeClass, availableVersions: string[], - vulnerableVersionRange?: string, + options?: BestPatchVersionOptions | undefined, ): string | null { + const { minSatisfying = false, vulnerableVersionRange } = { + __proto__: null, + ...options, + } as BestPatchVersionOptions const manifestData = getManifestData(NPM, node.name) let eligibleVersions if (manifestData && manifestData.name === manifestData.package) { @@ -68,7 +77,13 @@ export function findBestPatchVersion( !semver.satisfies(v, vulnerableVersionRange)), ) } - return eligibleVersions ? semver.maxSatisfying(eligibleVersions, '*') : null + if (eligibleVersions) { + const satisfying = minSatisfying + ? semver.minSatisfying + : semver.maxSatisfying + return satisfying(eligibleVersions, '*') + } + return null } export function findPackageNode(