From 4b62acc6163dc9c2941173f1e5d71d810ce9c2af Mon Sep 17 00:00:00 2001 From: Rhys Arkins <rhys@arkins.net> Date: Sun, 21 Feb 2021 23:09:53 +0100 Subject: [PATCH] refactor: file-based alert aggregation --- lib/workers/repository/init/vulnerability.ts | 179 +++++++++---------- 1 file changed, 89 insertions(+), 90 deletions(-) diff --git a/lib/workers/repository/init/vulnerability.ts b/lib/workers/repository/init/vulnerability.ts index 2e75589183..a2058d661e 100644 --- a/lib/workers/repository/init/vulnerability.ts +++ b/lib/workers/repository/init/vulnerability.ts @@ -19,18 +19,21 @@ import * as semverVersioning from '../../../versioning/semver'; type Datasource = string; type DependencyName = string; type FileName = string; +type VulnerableRequirements = string; type CombinedAlert = Record< - Datasource, + FileName, Record< - DependencyName, + Datasource, Record< - FileName, - { - advisories: SecurityAdvisory[]; - firstPatchedVersion?: string; - vulnerableRequirements?: string; - } + DependencyName, + Record< + VulnerableRequirements, + { + advisories: SecurityAdvisory[]; + firstPatchedVersion?: string; + } + > > > >; @@ -88,35 +91,36 @@ export async function detectVulnerabilityAlerts( const firstPatchedVersion = alert.securityVulnerability.firstPatchedVersion.identifier; const advisory = alert.securityAdvisory; - const { vulnerableRequirements } = alert; - if (!combinedAlerts[datasource]) { - combinedAlerts[datasource] = {}; - } - if (!combinedAlerts[datasource][depName]) { - combinedAlerts[datasource][depName] = {}; - } - if (!combinedAlerts[datasource][depName][fileName]) { - combinedAlerts[datasource][depName][fileName] = { - advisories: [], - }; + let { vulnerableRequirements } = alert; + // istanbul ignore if + if (!vulnerableRequirements.length) { + if (datasource === datasourceMaven.id) { + vulnerableRequirements = `(,${firstPatchedVersion})`; + } else { + vulnerableRequirements = `< ${firstPatchedVersion}`; + } } - const alertDetails = combinedAlerts[datasource][depName][fileName]; + combinedAlerts[fileName] ||= {}; + combinedAlerts[fileName][datasource] ||= {}; + combinedAlerts[fileName][datasource][depName] ||= {}; + combinedAlerts[fileName][datasource][depName][ + vulnerableRequirements + ] ||= { + advisories: [], + }; + const alertDetails = + combinedAlerts[fileName][datasource][depName][vulnerableRequirements]; alertDetails.advisories.push(advisory); const version = allVersioning.get(versionings[datasource]); if (version.isVersion(firstPatchedVersion)) { - if (alertDetails.firstPatchedVersion) { - if ( - version.isGreaterThan( - firstPatchedVersion, - alertDetails.firstPatchedVersion - ) - ) { - alertDetails.firstPatchedVersion = firstPatchedVersion; - alertDetails.vulnerableRequirements = vulnerableRequirements; - } - } else { + if ( + !alertDetails.firstPatchedVersion || + version.isGreaterThan( + firstPatchedVersion, + alertDetails.firstPatchedVersion + ) + ) { alertDetails.firstPatchedVersion = firstPatchedVersion; - alertDetails.vulnerableRequirements = vulnerableRequirements; } } else { logger.debug('Invalid firstPatchedVersion: ' + firstPatchedVersion); @@ -126,66 +130,61 @@ export async function detectVulnerabilityAlerts( } } const alertPackageRules = []; - for (const [datasource, dependencies] of Object.entries(combinedAlerts)) { - for (const [depName, files] of Object.entries(dependencies)) { - for (const [fileName, val] of Object.entries(files)) { - let prBodyNotes: string[] = []; - try { - prBodyNotes = ['### GitHub Vulnerability Alerts'].concat( - val.advisories.map((advisory) => { - let content = '#### '; - let heading: string; - if (advisory.identifiers.some((id) => id.type === 'CVE')) { - heading = advisory.identifiers - .filter((id) => id.type === 'CVE') - .map((id) => id.value) - .join(' / '); - } else { - heading = advisory.identifiers - .map((id) => id.value) - .join(' / '); - } - if (advisory.references.length) { - heading = `[${heading}](${advisory.references[0].url})`; - } - content += heading; - content += '\n\n'; - // eslint-disable-next-line no-loop-func - content += sanitizeMarkdown(advisory.description); - return content; - }) - ); - } catch (err) /* istanbul ignore next */ { - logger.warn({ err }, 'Error generating vulnerability PR notes'); - } - let matchCurrentVersion = val.vulnerableRequirements; - // istanbul ignore if - if (!matchCurrentVersion) { - if (datasource === datasourceMaven.id) { - matchCurrentVersion = `(,${val.firstPatchedVersion})`; - } else { - matchCurrentVersion = `< ${val.firstPatchedVersion}`; + for (const [fileName, files] of Object.entries(combinedAlerts)) { + for (const [datasource, dependencies] of Object.entries(files)) { + for (const [depName, currentValues] of Object.entries(dependencies)) { + for (const [vulnerableRequirements, val] of Object.entries( + currentValues + )) { + let prBodyNotes: string[] = []; + try { + prBodyNotes = ['### GitHub Vulnerability Alerts'].concat( + val.advisories.map((advisory) => { + let content = '#### '; + let heading: string; + if (advisory.identifiers.some((id) => id.type === 'CVE')) { + heading = advisory.identifiers + .filter((id) => id.type === 'CVE') + .map((id) => id.value) + .join(' / '); + } else { + heading = advisory.identifiers + .map((id) => id.value) + .join(' / '); + } + if (advisory.references.length) { + heading = `[${heading}](${advisory.references[0].url})`; + } + content += heading; + content += '\n\n'; + // eslint-disable-next-line no-loop-func + content += sanitizeMarkdown(advisory.description); + return content; + }) + ); + } catch (err) /* istanbul ignore next */ { + logger.warn({ err }, 'Error generating vulnerability PR notes'); } + const allowedVersions = + datasource === datasourcePypi.id + ? `==${val.firstPatchedVersion}` + : val.firstPatchedVersion; + const matchRule: PackageRule = { + matchDatasources: [datasource], + matchPackageNames: [depName], + matchCurrentVersion: vulnerableRequirements, + allowedVersions, + prBodyNotes, + force: { + ...config.vulnerabilityAlerts, + vulnerabilityAlert: true, + branchTopic: `${datasource}-${depName}-vulnerability`, + prCreation: 'immediate', + }, + }; + matchRule.matchFiles = [fileName]; + alertPackageRules.push(matchRule); } - const allowedVersions = - datasource === datasourcePypi.id - ? `==${val.firstPatchedVersion}` - : val.firstPatchedVersion; - const matchRule: PackageRule = { - matchDatasources: [datasource], - matchPackageNames: [depName], - matchCurrentVersion, - allowedVersions, - prBodyNotes, - force: { - ...config.vulnerabilityAlerts, - vulnerabilityAlert: true, - branchTopic: `${datasource}-${depName}-vulnerability`, - prCreation: 'immediate', - }, - }; - matchRule.matchFiles = [fileName]; - alertPackageRules.push(matchRule); } } } -- GitLab