Skip to content

Commit

Permalink
feat: vulnerabilityFixStrategy (#31395)
Browse files Browse the repository at this point in the history
Co-authored-by: Johannes Feichtner <343448+Churro@users.noreply.github.com>
  • Loading branch information
rarkins and Churro authored Sep 25, 2024
1 parent 1bd7f71 commit c56d68e
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 9 deletions.
15 changes: 15 additions & 0 deletions docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -4062,3 +4062,18 @@ To disable the vulnerability alerts feature, set `enabled=false` in a `vulnerabi
<!-- prettier-ignore -->
!!! note
If you want to raise only vulnerability fix PRs, you may use the `security:only-security-updates` preset.

### vulnerabilityFixStrategy

When a vulnerability fix is available, Renovate will default to picking the lowest fixed version (`vulnerabilityFixStrategy=lowest`).
For example, if the current version is `1.0.0`, and a vulnerability is fixed in `1.1.0`, while the latest version is `1.2.0`, then Renovate will propose an update to `1.1.0` as the vulnerability fix.

If `vulnerabilityFixStrategy=highest` is configured then Renovate will use its normal strategy for picking upgrades, e.g. in the above example it will propose an update to `1.2.0` to fix the vulnerability.

```json title="Setting vulnerabilityFixStrategy to highest"
{
"vulnerabilityAlerts": {
"vulnerabilityFixStrategy": "highest"
}
}
```
10 changes: 10 additions & 0 deletions lib/config/options/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1976,12 +1976,22 @@ const options: RenovateOptions[] = [
commitMessageSuffix: '[SECURITY]',
branchTopic: `{{{datasource}}}-{{{depNameSanitized}}}-vulnerability`,
prCreation: 'immediate',
vulnerabilityFixStrategy: 'lowest',
},
mergeable: true,
cli: false,
env: false,
supportedPlatforms: ['github'],
},
{
name: 'vulnerabilityFixStrategy',
description:
'Strategy to use when fixing vulnerabilities. `lowest` will propose the earliest version with a fix, `highest` will always pick the latest version.',
type: 'string',
allowedValues: ['lowest', 'highest'],
default: 'lowest',
parents: ['vulnerabilityAlerts'],
},
{
name: 'osvVulnerabilityAlerts',
description: 'Use vulnerability alerts from `osv.dev`.',
Expand Down
7 changes: 4 additions & 3 deletions lib/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,12 +392,13 @@ export interface ValidationMessage {
}

export type AllowedParents =
| 'customManagers'
| 'customDatasources'
| 'customManagers'
| 'hostRules'
| 'postUpgradeTasks'
| 'logLevelRemap'
| 'packageRules'
| 'logLevelRemap';
| 'postUpgradeTasks'
| 'vulnerabilityAlerts';
export interface RenovateOptionBase {
/**
* If true, the option can only be configured by people with access to the Renovate instance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur
"prCreation": "immediate",
"rangeStrategy": "update-lockfile",
"schedule": [],
"vulnerabilityFixStrategy": "lowest",
},
"isVulnerabilityAlert": true,
"matchDatasources": [
Expand Down Expand Up @@ -46,6 +47,7 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur
"prCreation": "immediate",
"rangeStrategy": "update-lockfile",
"schedule": [],
"vulnerabilityFixStrategy": "lowest",
},
"isVulnerabilityAlert": true,
"matchDatasources": [
Expand Down Expand Up @@ -80,6 +82,7 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur
"prCreation": "immediate",
"rangeStrategy": "update-lockfile",
"schedule": [],
"vulnerabilityFixStrategy": "lowest",
},
"isVulnerabilityAlert": true,
"matchDatasources": [
Expand Down
53 changes: 53 additions & 0 deletions lib/workers/repository/process/lookup/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,32 @@ describe('workers/repository/process/lookup/index', () => {
]);
});

it('uses highest available version for vulnerabilityAlerts when vulnerabilityFixStrategy=highest', async () => {
config.currentValue = '1.0.0';
config.isVulnerabilityAlert = true;
config.vulnerabilityFixStrategy = 'highest';
config.packageName = 'q';
config.datasource = NpmDatasource.id;
httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson);

const { updates } = await Result.wrap(
lookup.lookupUpdates(config),
).unwrapOrThrow();

expect(updates).toEqual([
{
bucket: 'non-major',
newMajor: 1,
newMinor: 4,
newPatch: 1,
newValue: '1.4.1',
newVersion: '1.4.1',
releaseTimestamp: expect.any(String),
updateType: 'minor',
},
]);
});

it('uses vulnerabilityFixVersion when a version', async () => {
config.currentValue = '1.0.0';
config.isVulnerabilityAlert = true;
Expand Down Expand Up @@ -896,6 +922,33 @@ describe('workers/repository/process/lookup/index', () => {
]);
});

it('takes highest available version when using vulnerabilityFixStrategy=highest with vulnerabilityFixVersion', async () => {
config.currentValue = '1.0.0';
config.isVulnerabilityAlert = true;
config.vulnerabilityFixVersion = '1.1.0';
config.vulnerabilityFixStrategy = 'highest';
config.packageName = 'q';
config.datasource = NpmDatasource.id;
httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson);

const { updates } = await Result.wrap(
lookup.lookupUpdates(config),
).unwrapOrThrow();

expect(updates).toEqual([
{
bucket: 'non-major',
newMajor: 1,
newMinor: 4,
newPatch: 1,
newValue: '1.4.1',
newVersion: '1.4.1',
releaseTimestamp: expect.any(String),
updateType: 'minor',
},
]);
});

it('ignores vulnerabilityFixVersion if not a version', async () => {
config.currentValue = '1.0.0';
config.isVulnerabilityAlert = true;
Expand Down
20 changes: 14 additions & 6 deletions lib/workers/repository/process/lookup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ export async function lookupUpdates(
if (config.isVulnerabilityAlert) {
if (config.vulnerabilityFixVersion) {
res.vulnerabilityFixVersion = config.vulnerabilityFixVersion;
res.vulnerabilityFixStrategy = config.vulnerabilityFixStrategy;
if (versioning.isValid(config.vulnerabilityFixVersion)) {
let fixedFilteredReleases;
if (versioning.isVersion(config.vulnerabilityFixVersion)) {
Expand Down Expand Up @@ -417,12 +418,19 @@ export async function lookupUpdates(
);
}
}
filteredReleases = filteredReleases.slice(0, 1);
shrinkedViaVulnerability = true;
logger.debug(
{ filteredReleases },
'Vulnerability alert found: limiting results to a single release',
);
if (config.vulnerabilityFixStrategy === 'highest') {
// Don't shrink the list of releases - let Renovate use its normal logic
logger.once.debug(
`Using vulnerabilityFixStrategy=highest for ${config.packageName}`,
);
} else {
// Shrink the list of releases to the lowest fixed version
logger.once.debug(
`Using vulnerabilityFixStrategy=lowest for ${config.packageName}`,
);
filteredReleases = filteredReleases.slice(0, 1);
shrinkedViaVulnerability = true;
}
}
const buckets: Record<string, [Release]> = {};
for (const release of filteredReleases) {
Expand Down
2 changes: 2 additions & 0 deletions lib/workers/repository/process/lookup/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface LookupUpdateConfig
replacementVersion?: string;
extractVersion?: string;
vulnerabilityFixVersion?: string;
vulnerabilityFixStrategy?: string;
}

export interface UpdateResult {
Expand All @@ -70,4 +71,5 @@ export interface UpdateResult {
versioning?: string;
currentVersionTimestamp?: string;
vulnerabilityFixVersion?: string;
vulnerabilityFixStrategy?: string;
}

0 comments on commit c56d68e

Please sign in to comment.