From a9ef562effed8b3e434356b6abe364ef9b4d67a7 Mon Sep 17 00:00:00 2001
From: Johannes Feichtner <343448+Churro@users.noreply.github.com>
Date: Mon, 13 Feb 2023 19:00:36 +0100
Subject: [PATCH] feat(vulnerabilities): add additional severity indicators and
 improve layout (#20363)

Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
---
 .../process/vulnerabilities.spec.ts           | 174 ++++++++++++++++--
 .../repository/process/vulnerabilities.ts     |  40 +++-
 2 files changed, 191 insertions(+), 23 deletions(-)

diff --git a/lib/workers/repository/process/vulnerabilities.spec.ts b/lib/workers/repository/process/vulnerabilities.spec.ts
index 8a03529383..dc3146ad5c 100644
--- a/lib/workers/repository/process/vulnerabilities.spec.ts
+++ b/lib/workers/repository/process/vulnerabilities.spec.ts
@@ -492,18 +492,18 @@ describe('workers/repository/process/vulnerabilities', () => {
               <details>
               <summary>More information</summary>
 
-              ### Details
+              #### Details
               HTTP pipelining issues and request smuggling attacks are possible due to incorrect Transfer encoding header parsing.
 
               It is possible conduct HTTP request smuggling attacks (CL:TE/TE:TE) by sending invalid Transfer Encoding headers.
 
               By manipulating the HTTP response the attacker could poison a web-cache, perform an XSS attack, or obtain sensitive information from requests other than their own.
 
-              ### Severity
-              - Score: 6.5 / 10 (Medium)
-              - Vector: \`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N\`
+              #### Severity
+              - CVSS Score: 6.5 / 10 (Medium)
+              - Vector String: \`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N\`
 
-              ### References
+              #### References
               No references.
 
               This data is provided by [OSV](https://osv.dev/vulnerability/RUSTSEC-2020-0031) and the [Rust Advisory Database](https://github.com/RustSec/advisory-db) ([CC0 1.0](https://github.com/rustsec/advisory-db/blob/main/LICENSE.txt)).
@@ -664,13 +664,13 @@ describe('workers/repository/process/vulnerabilities', () => {
               <details>
               <summary>More information</summary>
 
-              ### Details
+              #### Details
               No details.
 
-              ### Severity
+              #### Severity
               Unknown severity.
 
-              ### References
+              #### References
               No references.
 
               This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-xxxx-yyyy-zzzz) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
@@ -742,14 +742,14 @@ describe('workers/repository/process/vulnerabilities', () => {
               <details>
               <summary>More information</summary>
 
-              ### Details
+              #### Details
               No details.
 
-              ### Severity
-              - Score: Unknown
-              - Vector: \`some-invalid-score\`
+              #### Severity
+              - CVSS Score: Unknown
+              - Vector String: \`some-invalid-score\`
 
-              ### References
+              #### References
               No references.
 
               This data is provided by [OSV](https://osv.dev/vulnerability/PYSEC-2022-303) and the [PyPI Advisory Database](https://github.com/pypa/advisory-database) ([CC-BY 4.0](https://github.com/pypa/advisory-database/blob/main/LICENSE)).
@@ -759,5 +759,153 @@ describe('workers/repository/process/vulnerabilities', () => {
         },
       ]);
     });
+
+    it('show severity text in GHSA advisories without CVSS score', async () => {
+      const packageFiles: Record<string, PackageFile[]> = {
+        npm: [
+          {
+            deps: [
+              { depName: 'lodash', currentValue: '4.17.10', datasource: 'npm' },
+            ],
+          },
+        ],
+      };
+
+      getVulnerabilitiesMock.mockResolvedValueOnce([
+        {
+          ...lodashVulnerability,
+          database_specific: {
+            severity: 'MODERATE',
+          },
+        },
+      ]);
+
+      await vulnerabilities.fetchVulnerabilities(config, packageFiles);
+
+      expect(config.packageRules).toHaveLength(1);
+      expect(config.packageRules).toMatchObject([
+        {
+          matchDatasources: ['npm'],
+          matchPackageNames: ['lodash'],
+          matchCurrentVersion: '4.17.10',
+          allowedVersions: '4.17.11',
+          isVulnerabilityAlert: true,
+          prBodyNotes: [
+            '\n\n' +
+              codeBlock`
+              ---
+
+              ### [GHSA-x5rq-j2xg-h7qm](https://github.com/advisories/GHSA-x5rq-j2xg-h7qm)
+
+              <details>
+              <summary>More information</summary>
+
+              #### Details
+              No details.
+
+              #### Severity
+              Moderate
+
+              #### References
+              - [https://nvd.nist.gov/vuln/detail/CVE-2019-1010266](https://nvd.nist.gov/vuln/detail/CVE-2019-1010266)
+
+              This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-x5rq-j2xg-h7qm) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
+              </details>
+            `,
+          ],
+        },
+      ]);
+    });
+
+    it('formats headings of vulnerability details', async () => {
+      const packageFiles: Record<string, PackageFile[]> = {
+        regex: [
+          {
+            deps: [
+              {
+                depName: 'sys-info',
+                currentValue: '0.6.0',
+                datasource: 'crate',
+              },
+            ],
+          },
+        ],
+      };
+      getVulnerabilitiesMock.mockResolvedValueOnce([
+        {
+          id: 'RUSTSEC-2020-0100',
+          summary:
+            'Double free when calling `sys_info::disk_info` from multiple threads',
+          details:
+            'Affected versions of `sys-info` use a static, global, list to store temporary disk information while running. The function that cleans up this list,\n`DFCleanup`, assumes a single threaded environment and will try to free the same memory twice in a multithreaded environment.\n\nThis results in consistent double-frees and segfaults when calling `sys_info::disk_info` from multiple threads at once.\n\nThe issue was fixed by moving the global variable into a local scope.\n\n## Safer Alternatives:\n - [`sysinfo`](https://crates.io/crates/sysinfo)',
+          aliases: ['CVE-2020-36434'],
+          modified: '',
+          affected: [
+            {
+              package: {
+                name: 'sys-info',
+                ecosystem: 'crates.io',
+                purl: 'pkg:cargo/sys-info',
+              },
+              ranges: [
+                {
+                  type: 'SEMVER',
+                  events: [{ introduced: '0.0.0-0' }, { fixed: '0.8.0' }],
+                },
+              ],
+              database_specific: {
+                cvss: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H',
+              },
+            },
+          ],
+        },
+      ]);
+
+      await vulnerabilities.fetchVulnerabilities(config, packageFiles);
+
+      expect(config.packageRules).toHaveLength(1);
+      expect(config.packageRules).toMatchObject([
+        {
+          matchDatasources: ['crate'],
+          matchPackageNames: ['sys-info'],
+          matchCurrentVersion: '0.6.0',
+          allowedVersions: '0.8.0',
+          isVulnerabilityAlert: true,
+          prBodyNotes: [
+            '\n\n' +
+              codeBlock`
+              ---
+
+              ### Double free when calling \`sys_info::disk_info\` from multiple threads
+              [CVE-2020-36434](https://nvd.nist.gov/vuln/detail/CVE-2020-36434) / [RUSTSEC-2020-0100](https://rustsec.org/advisories/RUSTSEC-2020-0100.html)
+
+              <details>
+              <summary>More information</summary>
+
+              #### Details
+              Affected versions of \`sys-info\` use a static, global, list to store temporary disk information while running. The function that cleans up this list,
+              \`DFCleanup\`, assumes a single threaded environment and will try to free the same memory twice in a multithreaded environment.
+
+              This results in consistent double-frees and segfaults when calling \`sys_info::disk_info\` from multiple threads at once.
+
+              The issue was fixed by moving the global variable into a local scope.
+
+              ##### Safer Alternatives:
+               - [\`sysinfo\`](https://crates.io/crates/sysinfo)
+
+              #### Severity
+              - CVSS Score: 9.8 / 10 (Critical)
+              - Vector String: \`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\`
+
+              #### References
+              No references.
+
+              This data is provided by [OSV](https://osv.dev/vulnerability/RUSTSEC-2020-0100) and the [Rust Advisory Database](https://github.com/RustSec/advisory-db) ([CC0 1.0](https://github.com/rustsec/advisory-db/blob/main/LICENSE.txt)).
+              </details>
+            `,
+          ],
+        },
+      ]);
+    });
   });
 });
diff --git a/lib/workers/repository/process/vulnerabilities.ts b/lib/workers/repository/process/vulnerabilities.ts
index 3ab53afe7a..1b51870cdb 100644
--- a/lib/workers/repository/process/vulnerabilities.ts
+++ b/lib/workers/repository/process/vulnerabilities.ts
@@ -190,7 +190,8 @@ export class Vulnerabilities {
             packageName,
             depVersion,
             fixedVersion,
-            vulnerability
+            vulnerability,
+            affected
           );
           packageRules.push(rule);
         }
@@ -398,7 +399,8 @@ export class Vulnerabilities {
     packageName: string,
     depVersion: string,
     fixedVersion: string,
-    vulnerability: Osv.Vulnerability
+    vulnerability: Osv.Vulnerability,
+    affected: Osv.Affected
   ): PackageRule {
     return {
       matchDatasources: [dep.datasource!],
@@ -406,7 +408,7 @@ export class Vulnerabilities {
       matchCurrentVersion: depVersion,
       allowedVersions: fixedVersion,
       isVulnerabilityAlert: true,
-      prBodyNotes: this.generatePrBodyNotes(vulnerability),
+      prBodyNotes: this.generatePrBodyNotes(vulnerability, affected),
       force: {
         ...packageFileConfig.vulnerabilityAlerts,
       },
@@ -428,7 +430,10 @@ export class Vulnerabilities {
     return ['', ''];
   }
 
-  private generatePrBodyNotes(vulnerability: Osv.Vulnerability): string[] {
+  private generatePrBodyNotes(
+    vulnerability: Osv.Vulnerability,
+    affected: Osv.Affected
+  ): string[] {
     let aliases = [vulnerability.id].concat(vulnerability.aliases ?? []).sort();
     aliases = aliases.map((id) => {
       if (id.startsWith('CVE-')) {
@@ -448,22 +453,37 @@ export class Vulnerabilities {
     content += vulnerability.summary ? `${vulnerability.summary}\n` : '';
     content += `${aliases.join(' / ')}\n`;
     content += `\n<details>\n<summary>More information</summary>\n`;
-    content += `### Details\n${vulnerability.details ?? 'No details.'}\n`;
 
-    content += '### Severity\n';
+    const details = vulnerability.details?.replace(
+      regEx(/^#{1,4} /gm),
+      '##### '
+    );
+    content += `#### Details\n${details ?? 'No details.'}\n`;
+
+    content += '#### Severity\n';
     const cvssVector =
       vulnerability.severity?.find((e) => e.type === 'CVSS_V3')?.score ??
-      vulnerability.severity?.[0]?.score;
+      vulnerability.severity?.[0]?.score ??
+      (affected.database_specific?.cvss as string); // RUSTSEC
     if (cvssVector) {
       const [baseScore, severity] = this.evaluateCvssVector(cvssVector);
       const score = baseScore ? `${baseScore} / 10 (${severity})` : 'Unknown';
-      content += `- Score: ${score}\n`;
-      content += `- Vector: \`${cvssVector}\`\n`;
+      content += `- CVSS Score: ${score}\n`;
+      content += `- Vector String: \`${cvssVector}\`\n`;
+    } else if (
+      vulnerability.id.startsWith('GHSA-') &&
+      vulnerability.database_specific?.severity
+    ) {
+      const severity = vulnerability.database_specific.severity as string;
+      content +=
+        severity.charAt(0).toUpperCase() +
+        severity.slice(1).toLowerCase() +
+        '\n';
     } else {
       content += 'Unknown severity.\n';
     }
 
-    content += `\n### References\n${
+    content += `\n#### References\n${
       vulnerability.references
         ?.map((ref) => {
           return `- [${ref.url}](${ref.url})`;
-- 
GitLab