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