From 1c822181977c3bcd63e5b1f5715314580afceb22 Mon Sep 17 00:00:00 2001
From: Adam Setch <adam.setch@outlook.com>
Date: Tue, 16 May 2023 11:38:23 -0400
Subject: [PATCH] feat(vulnerabilities): handle medium and unknown severities
 (#22257)

Co-authored-by: Jamie Magee <jamie.magee@gmail.com>
---
 lib/util/string.ts                            | 11 ++++++++
 lib/util/template/index.ts                    |  2 +-
 lib/util/vulnerability/utils.spec.ts          | 28 +++++++++++++++++++
 lib/util/vulnerability/utils.ts               |  2 ++
 lib/workers/repository/process/types.ts       |  2 +-
 .../process/vulnerabilities.spec.ts           |  2 +-
 .../repository/process/vulnerabilities.ts     | 22 +++++++--------
 7 files changed, 54 insertions(+), 15 deletions(-)

diff --git a/lib/util/string.ts b/lib/util/string.ts
index 4030956cdd..7a4d7d8760 100644
--- a/lib/util/string.ts
+++ b/lib/util/string.ts
@@ -56,3 +56,14 @@ export function looseEquals(
 export function isDockerDigest(input: string): boolean {
   return /^sha256:[a-f0-9]{64}$/i.test(input);
 }
+
+export function titleCase(input: string): string {
+  const words = input.toLowerCase().split(' ');
+
+  for (let i = 0; i < words.length; i++) {
+    const word = words[i];
+    words[i] = word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
+  }
+
+  return words.join(' ');
+}
diff --git a/lib/util/template/index.ts b/lib/util/template/index.ts
index da16eaa90f..5c3e1261e2 100644
--- a/lib/util/template/index.ts
+++ b/lib/util/template/index.ts
@@ -145,7 +145,7 @@ export const allowedFields = {
   versioning: 'The versioning scheme in use',
   versions: 'An array of ChangeLogRelease objects in the upgrade',
   vulnerabilitySeverity:
-    'The severity for a vulnerability alert upgrade (eg: LOW, MODERATE, HIGH, CRITICAL)',
+    'The severity for a vulnerability alert upgrade (LOW, MEDIUM, MODERATE, HIGH, CRITICAL, UNKNOWN)',
 };
 
 const prBodyFields = [
diff --git a/lib/util/vulnerability/utils.spec.ts b/lib/util/vulnerability/utils.spec.ts
index 0d9aa2ee34..4ba086a539 100644
--- a/lib/util/vulnerability/utils.spec.ts
+++ b/lib/util/vulnerability/utils.spec.ts
@@ -85,6 +85,20 @@ describe('util/vulnerability/utils', () => {
     expect(severity).toBe('MODERATE');
   });
 
+  it('child MEDIUM vulnerability severity rating is maintained', () => {
+    const parentConfig = {
+      vulnerabilitySeverity: 'LOW',
+    };
+
+    const childConfig = {
+      vulnerabilitySeverity: 'MEDIUM',
+    };
+
+    const severity = getHighestVulnerabilitySeverity(parentConfig, childConfig);
+
+    expect(severity).toBe('MEDIUM');
+  });
+
   it('parent LOW vulnerability severity rating is maintained', () => {
     const parentConfig = {
       vulnerabilitySeverity: 'LOW',
@@ -113,6 +127,20 @@ describe('util/vulnerability/utils', () => {
     expect(severity).toBe('LOW');
   });
 
+  it('child UNKNOWN vulnerability severity rating is maintained', () => {
+    const parentConfig = {
+      vulnerabilitySeverity: 'CRITICAL',
+    };
+
+    const childConfig = {
+      vulnerabilitySeverity: 'UNKNOWN',
+    };
+
+    const severity = getHighestVulnerabilitySeverity(parentConfig, childConfig);
+
+    expect(severity).toBe('UNKNOWN');
+  });
+
   it('handled undefined parent and child vulnerability severity', () => {
     const parentConfig = {
       vulnerabilitySeverity: undefined,
diff --git a/lib/util/vulnerability/utils.ts b/lib/util/vulnerability/utils.ts
index 5607a6de92..bc451a7db5 100644
--- a/lib/util/vulnerability/utils.ts
+++ b/lib/util/vulnerability/utils.ts
@@ -1,8 +1,10 @@
 const severityOrder: Record<string, number> = {
   LOW: 1,
+  MEDIUM: 2,
   MODERATE: 2,
   HIGH: 3,
   CRITICAL: 4,
+  UNKNOWN: 5,
 };
 
 export function getHighestVulnerabilitySeverity<
diff --git a/lib/workers/repository/process/types.ts b/lib/workers/repository/process/types.ts
index e59c569da1..c0de869e16 100644
--- a/lib/workers/repository/process/types.ts
+++ b/lib/workers/repository/process/types.ts
@@ -21,5 +21,5 @@ export interface DependencyVulnerabilities {
 export interface SeverityDetails {
   cvssVector: string;
   score: string;
-  severityLevel?: string;
+  severityLevel: string;
 }
diff --git a/lib/workers/repository/process/vulnerabilities.spec.ts b/lib/workers/repository/process/vulnerabilities.spec.ts
index acfb042443..eb63590dea 100644
--- a/lib/workers/repository/process/vulnerabilities.spec.ts
+++ b/lib/workers/repository/process/vulnerabilities.spec.ts
@@ -944,7 +944,7 @@ describe('workers/repository/process/vulnerabilities', () => {
               No details.
 
               #### Severity
-              Unknown severity.
+              Unknown
 
               #### References
               No references.
diff --git a/lib/workers/repository/process/vulnerabilities.ts b/lib/workers/repository/process/vulnerabilities.ts
index 93483f716a..9ff98a803d 100644
--- a/lib/workers/repository/process/vulnerabilities.ts
+++ b/lib/workers/repository/process/vulnerabilities.ts
@@ -18,6 +18,7 @@ import {
 import { sanitizeMarkdown } from '../../../util/markdown';
 import * as p from '../../../util/promises';
 import { regEx } from '../../../util/regex';
+import { titleCase } from '../../../util/string';
 import type {
   DependencyVulnerabilities,
   SeverityDetails,
@@ -470,7 +471,7 @@ export class Vulnerabilities {
       matchCurrentVersion: depVersion,
       allowedVersions: fixedVersion,
       isVulnerabilityAlert: true,
-      vulnerabilitySeverity: severityDetails.severityLevel?.toUpperCase(),
+      vulnerabilitySeverity: severityDetails.severityLevel,
       prBodyNotes: this.generatePrBodyNotes(vulnerability, affected),
       force: {
         ...packageFileConfig.vulnerabilityAlerts,
@@ -481,9 +482,7 @@ export class Vulnerabilities {
   private evaluateCvssVector(vector: string): [string, string] {
     try {
       const parsedCvss: CvssScore = parseCvssVector(vector);
-      const severityLevel =
-        parsedCvss.cvss3OverallSeverityText.charAt(0).toUpperCase() +
-        parsedCvss.cvss3OverallSeverityText.slice(1);
+      const severityLevel = parsedCvss.cvss3OverallSeverityText;
 
       return [parsedCvss.baseScore.toFixed(1), severityLevel];
     } catch (err) {
@@ -532,10 +531,8 @@ export class Vulnerabilities {
     if (severityDetails.cvssVector) {
       content += `- CVSS Score: ${severityDetails.score}\n`;
       content += `- Vector String: \`${severityDetails.cvssVector}\`\n`;
-    } else if (severityDetails.severityLevel) {
-      content += `${severityDetails.severityLevel}\n`;
     } else {
-      content += 'Unknown severity.\n';
+      content += `${titleCase(severityDetails.severityLevel)}\n`;
     }
 
     content += `\n#### References\n${
@@ -566,7 +563,7 @@ export class Vulnerabilities {
     vulnerability: Osv.Vulnerability,
     affected: Osv.Affected
   ): SeverityDetails {
-    let severityLevel: string | undefined;
+    let severityLevel = 'UNKNOWN';
     let score = 'Unknown';
 
     const cvssVector =
@@ -576,15 +573,16 @@ export class Vulnerabilities {
 
     if (cvssVector) {
       const [baseScore, severity] = this.evaluateCvssVector(cvssVector);
-      severityLevel = severity;
-      score = baseScore ? `${baseScore} / 10 (${severityLevel})` : 'Unknown';
+      severityLevel = severity.toUpperCase();
+      score = baseScore
+        ? `${baseScore} / 10 (${titleCase(severityLevel)})`
+        : 'Unknown';
     } else if (
       vulnerability.id.startsWith('GHSA-') &&
       vulnerability.database_specific?.severity
     ) {
       const severity = vulnerability.database_specific.severity as string;
-      severityLevel =
-        severity.charAt(0).toUpperCase() + severity.slice(1).toLowerCase();
+      severityLevel = severity.toUpperCase();
     }
 
     return {
-- 
GitLab