Skip to content

Commit e72091c

Browse files
authored
feat(fix): add opt-in --allow-overrides flag (#1573)
socket fix can now write a package manager override when that is the only way to fix a vulnerability. This happens when a parent package declares a version range that rules out every fixed version of the vulnerable dependency. The flag is off by default. When it is set, socket fix passes --allow-overrides to Coana in both local and CI mode. Coana then writes an npm overrides, pnpm overrides, Yarn Berry resolutions or Rush globalOverrides entry scoped to the blocking parent. The forced version can sit outside the range the parent declares, so the parent should be tested afterwards. Coana only gets the flag when it is set, so runs keep working with Coana versions that do not know it yet.
1 parent a04d1b4 commit e72091c

9 files changed

Lines changed: 99 additions & 0 deletions

‎CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

77
## [Unreleased]
88

9+
### Added
10+
- `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.
11+
912
### Changed
1013
- Updated the Coana CLI to v `15.12.0`.
1114

‎src/commands/fix/cmd-fix.integration.test.mts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ describe('socket fix', async () => {
164164
165165
Options
166166
--all Process all discovered vulnerabilities in local mode. Cannot be used with --id.
167+
--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.
167168
--autopilot Enable auto-merge for pull requests that Socket opens.
168169
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.
169170
--debug Enable debug logging in the Coana-based Socket Fix CLI invocation.
@@ -398,6 +399,22 @@ describe('socket fix', async () => {
398399
},
399400
)
400401

402+
cmdit(
403+
[
404+
'fix',
405+
FLAG_DRY_RUN,
406+
'--allow-overrides',
407+
FLAG_CONFIG,
408+
'{"apiToken":"fakeToken"}',
409+
],
410+
'should accept --allow-overrides flag',
411+
async cmd => {
412+
const { code, stdout } = await spawnSocketCli(binCliPath, cmd)
413+
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Not saving"`)
414+
expect(code, 'should exit with code 0').toBe(0)
415+
},
416+
)
417+
401418
cmdit(
402419
[
403420
'fix',

‎src/commands/fix/cmd-fix.mts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ export const cmdFix = {
5757
}
5858

5959
const generalFlags: MeowFlags = {
60+
allowOverrides: {
61+
type: 'boolean',
62+
default: false,
63+
description:
64+
"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.",
65+
},
6066
autopilot: {
6167
type: 'boolean',
6268
default: false,
@@ -327,6 +333,7 @@ async function run(
327333

328334
const {
329335
all,
336+
allowOverrides,
330337
applyFixes,
331338
autopilot,
332339
debug,
@@ -354,6 +361,7 @@ async function run(
354361
unknownFlags = [],
355362
} = cli.flags as {
356363
all: boolean
364+
allowOverrides: boolean
357365
applyFixes: boolean
358366
autopilot: boolean
359367
debug: boolean
@@ -520,6 +528,7 @@ async function run(
520528

521529
await handleFix({
522530
all,
531+
allowOverrides,
523532
applyFixes,
524533
autopilot,
525534
coanaVersion: fixVersion,

‎src/commands/fix/coana-fix-dynamic-sbom-inference.test.mts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ function coanaCalls(command: string): string[][] {
9494
describe('socket fix --dynamic-sbom-inference', () => {
9595
const baseConfig: FixConfig = {
9696
all: false,
97+
allowOverrides: false,
9798
applyFixes: true,
9899
autopilot: false,
99100
coanaVersion: undefined,

‎src/commands/fix/coana-fix-pr-files.test.mts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ function committedFiles(): string[] {
8484
describe('socket fix PR mode commits', () => {
8585
const baseConfig: FixConfig = {
8686
all: false,
87+
allowOverrides: false,
8788
applyFixes: true,
8889
autopilot: false,
8990
coanaVersion: undefined,

‎src/commands/fix/coana-fix.mts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ async function coanaFixWithFacts(
276276
): Promise<CoanaFixResult> {
277277
const {
278278
all,
279+
allowOverrides,
279280
applyFixes,
280281
autopilot,
281282
coanaVersion,
@@ -522,6 +523,7 @@ async function coanaFixWithFacts(
522523
? ['--disable-external-tool-checks']
523524
: []),
524525
...(disableMajorUpdates ? ['--disable-major-updates'] : []),
526+
...(allowOverrides ? ['--allow-overrides'] : []),
525527
...(showAffectedDirectDependencies
526528
? ['--show-affected-direct-dependencies']
527529
: []),
@@ -699,6 +701,7 @@ async function coanaFixWithFacts(
699701
? ['--disable-external-tool-checks']
700702
: []),
701703
...(disableMajorUpdates ? ['--disable-major-updates'] : []),
704+
...(allowOverrides ? ['--allow-overrides'] : []),
702705
...(showAffectedDirectDependencies
703706
? ['--show-affected-direct-dependencies']
704707
: []),

‎src/commands/fix/handle-fix-limit.test.mts‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ function mockDiscoveryEnvelope(envelope: {
107107
describe('socket fix --pr-limit behavior verification', () => {
108108
const baseConfig: FixConfig = {
109109
all: false,
110+
allowOverrides: false,
110111
applyFixes: true,
111112
autopilot: false,
112113
coanaVersion: undefined,
@@ -691,4 +692,64 @@ describe('socket fix --pr-limit behavior verification', () => {
691692
])
692693
})
693694
})
695+
696+
describe('--allow-overrides flag', () => {
697+
it('forwards --allow-overrides to coana in local mode', async () => {
698+
mockSpawnCoanaDlx.mockResolvedValue({ ok: true, data: 'fix applied' })
699+
700+
await coanaFix({
701+
...baseConfig,
702+
allowOverrides: true,
703+
ghsas: ['GHSA-1111-1111-1111'],
704+
})
705+
706+
expect(mockSpawnCoanaDlx).toHaveBeenCalledTimes(1)
707+
const callArgs = mockSpawnCoanaDlx.mock.calls[0]?.[0] as string[]
708+
expect(callArgs[0]).toBe('compute-fixes-and-upgrade-purls')
709+
expect(callArgs).toContain('--allow-overrides')
710+
})
711+
712+
it('omits --allow-overrides when the flag is not set', async () => {
713+
mockSpawnCoanaDlx.mockResolvedValue({ ok: true, data: 'fix applied' })
714+
715+
await coanaFix({
716+
...baseConfig,
717+
ghsas: ['GHSA-1111-1111-1111'],
718+
})
719+
720+
expect(mockSpawnCoanaDlx).toHaveBeenCalledTimes(1)
721+
const callArgs = mockSpawnCoanaDlx.mock.calls[0]?.[0] as string[]
722+
expect(callArgs).not.toContain('--allow-overrides')
723+
})
724+
725+
it('forwards --allow-overrides to coana in PR mode', async () => {
726+
mockGetFixEnv.mockResolvedValue({
727+
baseBranch: 'main',
728+
githubToken: 'test-token',
729+
gitEmail: 'test@example.com',
730+
gitUser: 'test-user',
731+
isCi: true,
732+
repoInfo: {
733+
defaultBranch: 'main',
734+
owner: 'test-owner',
735+
repo: 'test-repo',
736+
},
737+
})
738+
mockGetSocketFixPrs.mockResolvedValue([])
739+
mockFetchGhsaDetails.mockResolvedValue(new Map())
740+
mockSpawnCoanaDlx.mockResolvedValue({ ok: true, data: 'fix applied' })
741+
742+
await coanaFix({
743+
...baseConfig,
744+
allowOverrides: true,
745+
ghsas: ['GHSA-1111-1111-1111'],
746+
})
747+
748+
expect(mockSpawnCoanaDlx).toHaveBeenCalledTimes(1)
749+
const callArgs = mockSpawnCoanaDlx.mock.calls[0]?.[0] as string[]
750+
expect(callArgs[0]).toBe('compute-fixes-and-upgrade-purls')
751+
expect(callArgs).toContain('GHSA-1111-1111-1111')
752+
expect(callArgs).toContain('--allow-overrides')
753+
})
754+
})
694755
})

‎src/commands/fix/handle-fix.mts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export async function convertIdsToGhsas(
115115

116116
export async function handleFix({
117117
all,
118+
allowOverrides,
118119
applyFixes,
119120
autopilot,
120121
coanaVersion,
@@ -145,6 +146,7 @@ export async function handleFix({
145146
debugFn('notice', `Starting fix command for ${orgSlug}`)
146147
debugDir('inspect', {
147148
all,
149+
allowOverrides,
148150
applyFixes,
149151
autopilot,
150152
coanaVersion,
@@ -174,6 +176,7 @@ export async function handleFix({
174176
await outputFixResult(
175177
await coanaFix({
176178
all,
179+
allowOverrides,
177180
applyFixes,
178181
autopilot,
179182
coanaVersion,

‎src/commands/fix/types.mts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Spinner } from '@socketsecurity/registry/lib/spinner'
44

55
export type FixConfig = {
66
all: boolean
7+
allowOverrides: boolean
78
applyFixes: boolean
89
autopilot: boolean
910
coanaVersion: string | undefined

0 commit comments

Comments
 (0)