Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: remediate to greater than or equal versions for github alerts #31393

Merged
merged 11 commits into from
Sep 18, 2024
1 change: 1 addition & 0 deletions lib/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ export interface PackageRule
matchUpdateTypes?: UpdateType[];
registryUrls?: string[] | null;
vulnerabilitySeverity?: string;
vulnerabilityFixVersion?: string;
}

export interface ValidationMessage {
Expand Down
1 change: 1 addition & 0 deletions lib/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const ignoredNodes = [
'vulnerabilityAlertsOnly',
'vulnerabilityAlert',
'isVulnerabilityAlert',
'vulnerabilityFixVersion', // not intended to be used by end users but may be by Mend apps
'copyLocalLibs', // deprecated - functionality is now enabled by default
'prBody', // deprecated
'minimumConfidence', // undocumented feature flag
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() returns go alerts 1`] = `
[
{
"allowedVersions": "1.8.3",
"force": {
"branchTopic": "{{{datasource}}}-{{{depNameSanitized}}}-vulnerability",
"commitMessageSuffix": "[SECURITY]",
Expand All @@ -30,14 +29,14 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur

go",
],
"vulnerabilityFixVersion": "1.8.3",
},
]
`;

exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() returns maven alerts 1`] = `
[
{
"allowedVersions": "2.7.9.4",
"force": {
"branchTopic": "{{{datasource}}}-{{{depNameSanitized}}}-vulnerability",
"commitMessageSuffix": "[SECURITY]",
Expand All @@ -64,14 +63,14 @@ exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() retur

An issue was discovered in FasterXML jackson-databind prior to 2.7.9.4, 2.8.11.2, and 2.9.6. When Default Typing is enabled (either globally or for a specific property), the service has the Jodd-db jar (for database access for the Jodd framework) in the classpath, and an attacker can provide an LDAP service to access, it is possible to make the service execute a malicious payload.",
],
"vulnerabilityFixVersion": "2.7.9.4",
},
]
`;

exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() returns pip alerts 1`] = `
[
{
"allowedVersions": "==2.2.1.0",
"force": {
"branchTopic": "{{{datasource}}}-{{{depNameSanitized}}}-vulnerability",
"commitMessageSuffix": "[SECURITY]",
Expand Down Expand Up @@ -113,6 +112,7 @@ Ansible before version 2.2.0 fails to properly sanitize fact variables sent from

Ansible before versions 2.1.4, 2.2.1 is vulnerable to an improper input validation in Ansible's handling of data sent from client systems. An attacker with control over a client system being managed by Ansible and the ability to send facts back to the Ansible server could use this flaw to execute arbitrary code on the Ansible server using the Ansible server privileges.",
],
"vulnerabilityFixVersion": "2.2.1.0",
},
]
`;
6 changes: 1 addition & 5 deletions lib/workers/repository/init/vulnerability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,6 @@ export async function detectVulnerabilityAlerts(
logger.warn({ err }, 'Error generating vulnerability PR notes');
}
// TODO: types (#22198)
const allowedVersions =
datasource === PypiDatasource.id
? `==${val.firstPatchedVersion!}`
rarkins marked this conversation as resolved.
Show resolved Hide resolved
: val.firstPatchedVersion;
const matchFileNames =
datasource === GoDatasource.id
? [fileName.replace('go.sum', 'go.mod')]
Expand All @@ -191,7 +187,7 @@ export async function detectVulnerabilityAlerts(
// Remediate only direct dependencies
matchRule = {
...matchRule,
allowedVersions,
vulnerabilityFixVersion: val.firstPatchedVersion,
prBodyNotes,
isVulnerabilityAlert: true,
force: {
Expand Down
119 changes: 119 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,125 @@ describe('workers/repository/process/lookup/index', () => {
]);
});

it('uses vulnerabilityFixVersion when a version', async () => {
config.currentValue = '1.0.0';
config.isVulnerabilityAlert = true;
config.vulnerabilityFixVersion = '1.1.0';
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: 1,
newPatch: 0,
newValue: '1.1.0',
newVersion: '1.1.0',
releaseTimestamp: expect.any(String),
updateType: 'minor',
},
]);
});

it('takes a later release when vulnerabilityFixVersion does not exist', async () => {
config.currentValue = '1.0.0';
config.isVulnerabilityAlert = true;
config.vulnerabilityFixVersion = '1.0.2';
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: 1,
newPatch: 0,
newValue: '1.1.0',
newVersion: '1.1.0',
releaseTimestamp: expect.any(String),
updateType: 'minor',
},
]);
});

it('uses vulnerabilityFixVersion when a range', async () => {
config.currentValue = '1.0.0';
config.isVulnerabilityAlert = true;
config.vulnerabilityFixVersion = '>= 1.1.0';
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: 1,
newPatch: 0,
newValue: '1.1.0',
newVersion: '1.1.0',
releaseTimestamp: expect.any(String),
updateType: 'minor',
},
]);
});

it('ignores vulnerabilityFixVersion if not a version', async () => {
config.currentValue = '1.0.0';
config.isVulnerabilityAlert = true;
config.vulnerabilityFixVersion = 'abc';
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: 0,
newPatch: 1,
newValue: '1.0.1',
newVersion: '1.0.1',
releaseTimestamp: expect.any(String),
updateType: 'patch',
},
]);
});

it('returns no results if vulnerabilityFixVersion is too high', async () => {
config.currentValue = '1.0.0';
config.isVulnerabilityAlert = true;
config.vulnerabilityFixVersion = '5.1.0';
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).toBeEmptyArray();
});

it('supports minor and major upgrades for ranged versions', async () => {
config.currentValue = '~0.4.0';
config.rangeStrategy = 'pin';
Expand Down
45 changes: 45 additions & 0 deletions lib/workers/repository/process/lookup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,51 @@ export async function lookupUpdates(
);
let shrinkedViaVulnerability = false;
if (config.isVulnerabilityAlert) {
if (config.vulnerabilityFixVersion) {
res.vulnerabilityFixVersion = config.vulnerabilityFixVersion;
if (versioning.isValid(config.vulnerabilityFixVersion)) {
let fixedFilteredReleases;
if (versioning.isVersion(config.vulnerabilityFixVersion)) {
// Retain only releases greater than or equal to the fix version
fixedFilteredReleases = filteredReleases.filter(
(release) =>
!versioning.isGreaterThan(
config.vulnerabilityFixVersion!,
rarkins marked this conversation as resolved.
Show resolved Hide resolved
release.version,
),
);
} else {
// Retain only releases which max the fix constraint
fixedFilteredReleases = filteredReleases.filter((release) =>
versioning.matches(
release.version,
config.vulnerabilityFixVersion!,
),
);
}
// Warn if this filtering results caused zero releases
if (fixedFilteredReleases.length === 0 && filteredReleases.length) {
logger.warn(
{
releases: filteredReleases,
vulnerabilityFixVersion: config.vulnerabilityFixVersion,
packageName: config.packageName,
},
'No releases satisfy vulnerabilityFixVersion',
);
}
// Use the additionally filtered releases
filteredReleases = fixedFilteredReleases;
} else {
logger.warn(
{
vulnerabilityFixVersion: config.vulnerabilityFixVersion,
packageName: config.packageName,
},
'vulnerabilityFixVersion is not valid',
);
}
}
filteredReleases = filteredReleases.slice(0, 1);
shrinkedViaVulnerability = true;
logger.debug(
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 @@ -49,6 +49,7 @@ export interface LookupUpdateConfig
replacementNameTemplate?: string;
replacementVersion?: string;
extractVersion?: string;
vulnerabilityFixVersion?: string;
}

export interface UpdateResult {
Expand All @@ -68,4 +69,5 @@ export interface UpdateResult {
warnings: ValidationMessage[];
versioning?: string;
currentVersionTimestamp?: string;
vulnerabilityFixVersion?: string;
}