diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e7178c76..95ae6c5dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [Unreleased] + +### Added +- `socket fix --allow-overrides` fixes a vulnerability that a parent package's version range blocks by writing an override or resolution that forces the fixed version under that parent, in npm, pnpm, Yarn Berry and Rush projects. + ## [1.4.1](https://github.com/SocketDev/socket-cli/releases/tag/v1.4.1) - 2026-09-30 ### Changed diff --git a/src/commands/fix/cmd-fix.integration.test.mts b/src/commands/fix/cmd-fix.integration.test.mts index 0e364c379..03c64713c 100644 --- a/src/commands/fix/cmd-fix.integration.test.mts +++ b/src/commands/fix/cmd-fix.integration.test.mts @@ -164,6 +164,7 @@ describe('socket fix', async () => { Options --all Process all discovered vulnerabilities in local mode. Cannot be used with --id. + --allow-overrides When the only fix for a vulnerability is blocked by a parent package's declared version range, write an override or resolution that forces the fixed version under that parent. This can install a version outside the range the parent declares, so test the parent afterwards. Works for npm, pnpm, Yarn Berry and Rush projects. --autopilot Enable auto-merge for pull requests that Socket opens. See GitHub documentation (https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-auto-merge-for-pull-requests-in-your-repository) for managing auto-merge for pull requests in your repository. --debug Enable debug logging in the Coana-based Socket Fix CLI invocation. @@ -398,6 +399,22 @@ describe('socket fix', async () => { }, ) + cmdit( + [ + 'fix', + FLAG_DRY_RUN, + '--allow-overrides', + FLAG_CONFIG, + '{"apiToken":"fakeToken"}', + ], + 'should accept --allow-overrides flag', + async cmd => { + const { code, stdout } = await spawnSocketCli(binCliPath, cmd) + expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Not saving"`) + expect(code, 'should exit with code 0').toBe(0) + }, + ) + cmdit( [ 'fix', diff --git a/src/commands/fix/cmd-fix.mts b/src/commands/fix/cmd-fix.mts index 684b8cde0..6ff0a6f7e 100644 --- a/src/commands/fix/cmd-fix.mts +++ b/src/commands/fix/cmd-fix.mts @@ -57,6 +57,12 @@ export const cmdFix = { } const generalFlags: MeowFlags = { + allowOverrides: { + type: 'boolean', + default: false, + description: + "When the only fix for a vulnerability is blocked by a parent package's declared version range, write an override or resolution that forces the fixed version under that parent. This can install a version outside the range the parent declares, so test the parent afterwards. Works for npm, pnpm, Yarn Berry and Rush projects.", + }, autopilot: { type: 'boolean', default: false, @@ -327,6 +333,7 @@ async function run( const { all, + allowOverrides, applyFixes, autopilot, debug, @@ -354,6 +361,7 @@ async function run( unknownFlags = [], } = cli.flags as { all: boolean + allowOverrides: boolean applyFixes: boolean autopilot: boolean debug: boolean @@ -520,6 +528,7 @@ async function run( await handleFix({ all, + allowOverrides, applyFixes, autopilot, coanaVersion: fixVersion, diff --git a/src/commands/fix/coana-fix-dynamic-sbom-inference.test.mts b/src/commands/fix/coana-fix-dynamic-sbom-inference.test.mts index 7a5fddbae..19f001d0a 100644 --- a/src/commands/fix/coana-fix-dynamic-sbom-inference.test.mts +++ b/src/commands/fix/coana-fix-dynamic-sbom-inference.test.mts @@ -94,6 +94,7 @@ function coanaCalls(command: string): string[][] { describe('socket fix --dynamic-sbom-inference', () => { const baseConfig: FixConfig = { all: false, + allowOverrides: false, applyFixes: true, autopilot: false, coanaVersion: undefined, diff --git a/src/commands/fix/coana-fix-pr-files.test.mts b/src/commands/fix/coana-fix-pr-files.test.mts index 633610469..ae26aaa91 100644 --- a/src/commands/fix/coana-fix-pr-files.test.mts +++ b/src/commands/fix/coana-fix-pr-files.test.mts @@ -84,6 +84,7 @@ function committedFiles(): string[] { describe('socket fix PR mode commits', () => { const baseConfig: FixConfig = { all: false, + allowOverrides: false, applyFixes: true, autopilot: false, coanaVersion: undefined, diff --git a/src/commands/fix/coana-fix.mts b/src/commands/fix/coana-fix.mts index e76a91e33..6b9191490 100644 --- a/src/commands/fix/coana-fix.mts +++ b/src/commands/fix/coana-fix.mts @@ -276,6 +276,7 @@ async function coanaFixWithFacts( ): Promise { const { all, + allowOverrides, applyFixes, autopilot, coanaVersion, @@ -522,6 +523,7 @@ async function coanaFixWithFacts( ? ['--disable-external-tool-checks'] : []), ...(disableMajorUpdates ? ['--disable-major-updates'] : []), + ...(allowOverrides ? ['--allow-overrides'] : []), ...(showAffectedDirectDependencies ? ['--show-affected-direct-dependencies'] : []), @@ -699,6 +701,7 @@ async function coanaFixWithFacts( ? ['--disable-external-tool-checks'] : []), ...(disableMajorUpdates ? ['--disable-major-updates'] : []), + ...(allowOverrides ? ['--allow-overrides'] : []), ...(showAffectedDirectDependencies ? ['--show-affected-direct-dependencies'] : []), diff --git a/src/commands/fix/handle-fix-limit.test.mts b/src/commands/fix/handle-fix-limit.test.mts index bf83ecf21..19da2b943 100644 --- a/src/commands/fix/handle-fix-limit.test.mts +++ b/src/commands/fix/handle-fix-limit.test.mts @@ -107,6 +107,7 @@ function mockDiscoveryEnvelope(envelope: { describe('socket fix --pr-limit behavior verification', () => { const baseConfig: FixConfig = { all: false, + allowOverrides: false, applyFixes: true, autopilot: false, coanaVersion: undefined, @@ -691,4 +692,64 @@ describe('socket fix --pr-limit behavior verification', () => { ]) }) }) + + describe('--allow-overrides flag', () => { + it('forwards --allow-overrides to coana in local mode', async () => { + mockSpawnCoanaDlx.mockResolvedValue({ ok: true, data: 'fix applied' }) + + await coanaFix({ + ...baseConfig, + allowOverrides: true, + ghsas: ['GHSA-1111-1111-1111'], + }) + + expect(mockSpawnCoanaDlx).toHaveBeenCalledTimes(1) + const callArgs = mockSpawnCoanaDlx.mock.calls[0]?.[0] as string[] + expect(callArgs[0]).toBe('compute-fixes-and-upgrade-purls') + expect(callArgs).toContain('--allow-overrides') + }) + + it('omits --allow-overrides when the flag is not set', async () => { + mockSpawnCoanaDlx.mockResolvedValue({ ok: true, data: 'fix applied' }) + + await coanaFix({ + ...baseConfig, + ghsas: ['GHSA-1111-1111-1111'], + }) + + expect(mockSpawnCoanaDlx).toHaveBeenCalledTimes(1) + const callArgs = mockSpawnCoanaDlx.mock.calls[0]?.[0] as string[] + expect(callArgs).not.toContain('--allow-overrides') + }) + + it('forwards --allow-overrides to coana in PR mode', async () => { + mockGetFixEnv.mockResolvedValue({ + baseBranch: 'main', + githubToken: 'test-token', + gitEmail: 'test@example.com', + gitUser: 'test-user', + isCi: true, + repoInfo: { + defaultBranch: 'main', + owner: 'test-owner', + repo: 'test-repo', + }, + }) + mockGetSocketFixPrs.mockResolvedValue([]) + mockFetchGhsaDetails.mockResolvedValue(new Map()) + mockSpawnCoanaDlx.mockResolvedValue({ ok: true, data: 'fix applied' }) + + await coanaFix({ + ...baseConfig, + allowOverrides: true, + ghsas: ['GHSA-1111-1111-1111'], + }) + + expect(mockSpawnCoanaDlx).toHaveBeenCalledTimes(1) + const callArgs = mockSpawnCoanaDlx.mock.calls[0]?.[0] as string[] + expect(callArgs[0]).toBe('compute-fixes-and-upgrade-purls') + expect(callArgs).toContain('GHSA-1111-1111-1111') + expect(callArgs).toContain('--allow-overrides') + }) + }) }) diff --git a/src/commands/fix/handle-fix.mts b/src/commands/fix/handle-fix.mts index dbf83ba9b..65f0617f6 100644 --- a/src/commands/fix/handle-fix.mts +++ b/src/commands/fix/handle-fix.mts @@ -115,6 +115,7 @@ export async function convertIdsToGhsas( export async function handleFix({ all, + allowOverrides, applyFixes, autopilot, coanaVersion, @@ -145,6 +146,7 @@ export async function handleFix({ debugFn('notice', `Starting fix command for ${orgSlug}`) debugDir('inspect', { all, + allowOverrides, applyFixes, autopilot, coanaVersion, @@ -174,6 +176,7 @@ export async function handleFix({ await outputFixResult( await coanaFix({ all, + allowOverrides, applyFixes, autopilot, coanaVersion, diff --git a/src/commands/fix/types.mts b/src/commands/fix/types.mts index a14500fc8..e3a70d79e 100644 --- a/src/commands/fix/types.mts +++ b/src/commands/fix/types.mts @@ -4,6 +4,7 @@ import type { Spinner } from '@socketsecurity/registry/lib/spinner' export type FixConfig = { all: boolean + allowOverrides: boolean applyFixes: boolean autopilot: boolean coanaVersion: string | undefined