From eeffd45d5f7df60c29149b6f8c349137250d06d7 Mon Sep 17 00:00:00 2001 From: Johannes Feichtner Date: Sun, 12 Apr 2026 19:21:33 +0200 Subject: [PATCH] feat(vulnerability): add severity and CVSS details to GitHub Dependabot alerts --- lib/modules/platform/github/index.spec.ts | 14 ++ lib/modules/platform/github/schema.spec.ts | 43 ++++++ lib/modules/platform/github/schema.ts | 15 ++ .../repository/init/vulnerability.spec.ts | 130 ++++++++++++++++-- lib/workers/repository/init/vulnerability.ts | 24 +++- 5 files changed, 215 insertions(+), 11 deletions(-) diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index d63c9907691..501099b2448 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -4762,12 +4762,14 @@ describe('modules/platform/github/index', () => { description: 'description', identifiers: [{ type: 'type', value: 'value' }], references: [], + severity: 'high', }, security_vulnerability: { package: { ecosystem: 'npm', name: 'left-pad', }, + severity: 'high', vulnerable_version_range: '0.0.2', first_patched_version: { identifier: '0.0.3' }, }, @@ -4780,6 +4782,7 @@ describe('modules/platform/github/index', () => { description: 'description', identifiers: [{ type: 'type', value: 'value' }], references: [], + severity: 'high', }, security_vulnerability: null, dependency: { @@ -4833,12 +4836,14 @@ describe('modules/platform/github/index', () => { description: 'description', identifiers: [{ type: 'type', value: 'value' }], references: [], + severity: 'high', }, security_vulnerability: { package: { ecosystem: 'npm', name: 'left-pad', }, + severity: 'high', vulnerable_version_range: '0.0.2', first_patched_version: { identifier: '0.0.3' }, }, @@ -4852,6 +4857,7 @@ describe('modules/platform/github/index', () => { description: 'description', identifiers: [{ type: 'type', value: 'value' }], references: [], + severity: 'high', }, security_vulnerability: null, dependency: { @@ -4882,12 +4888,14 @@ describe('modules/platform/github/index', () => { description: 'description', identifiers: [{ type: 'type', value: 'value' }], references: [], + severity: 'medium', }, security_vulnerability: { package: { ecosystem: 'pip', name: 'FrIeNdLy.-.BARD', }, + severity: 'medium', vulnerable_version_range: '0.0.2', first_patched_version: { identifier: '0.0.3' }, }, @@ -4917,12 +4925,14 @@ describe('modules/platform/github/index', () => { description: 'description', identifiers: [{ type: 'type', value: 'value' }], references: [], + severity: 'high', }, security_vulnerability: { package: { ecosystem: 'npm', name: 'left-pad', }, + severity: 'high', vulnerable_version_range: '0.0.2', first_patched_version: { identifier: '0.0.3' }, }, @@ -4935,12 +4945,14 @@ describe('modules/platform/github/index', () => { description: 'description', identifiers: [{ type: 'type', value: 'value' }], references: [], + severity: 'critical', }, security_vulnerability: { package: { ecosystem: 'npm', name: 'right-pad', }, + severity: 'critical', vulnerable_version_range: '0.0.1', first_patched_version: { identifier: '0.0.2' }, }, @@ -4962,12 +4974,14 @@ describe('modules/platform/github/index', () => { description: 'description', identifiers: [{ type: 'type', value: 'value' }], references: [], + severity: 'low', }, security_vulnerability: { package: { ecosystem: 'npm', name: 'center-pad', }, + severity: 'low', vulnerable_version_range: '0.0.3', first_patched_version: { identifier: '0.0.4' }, }, diff --git a/lib/modules/platform/github/schema.spec.ts b/lib/modules/platform/github/schema.spec.ts index a8c76b2aad6..18ffcca7e34 100644 --- a/lib/modules/platform/github/schema.spec.ts +++ b/lib/modules/platform/github/schema.spec.ts @@ -115,10 +115,12 @@ describe('modules/platform/github/schema', () => { security_advisory: { description: 'Test advisory', identifiers: [{ type: 'CVE', value: 'CVE-2024-1234' }], + severity: 'high', }, security_vulnerability: { first_patched_version: { identifier: '1.0.0' }, package: { ecosystem: 'dotnet', name: 'test-package' }, + severity: 'high', vulnerable_version_range: '< 1.0.0', }, dependency: { manifest_path: 'package.json' }, @@ -128,10 +130,12 @@ describe('modules/platform/github/schema', () => { security_advisory: { description: 'Test advisory', identifiers: [{ type: 'CVE', value: 'CVE-2024-5678' }], + severity: 'medium', }, security_vulnerability: { first_patched_version: { identifier: '2.0.0' }, package: { ecosystem: 'npm', name: 'valid-package' }, + severity: 'medium', vulnerable_version_range: '< 2.0.0', }, dependency: { manifest_path: 'package.json' }, @@ -148,10 +152,12 @@ describe('modules/platform/github/schema', () => { security_advisory: { description: 'Test advisory', identifiers: [{ type: 'CVE', value: 'CVE-2024-1234' }], + severity: 'high', }, security_vulnerability: { first_patched_version: { identifier: '1.0.0' }, package: { ecosystem: 'dotnet', name: 'test-package' }, + severity: 'high', vulnerable_version_range: '< 1.0.0', }, }, @@ -173,6 +179,7 @@ describe('modules/platform/github/schema', () => { security_advisory: { description: 'Test advisory', identifiers: [{ type: 'CVE', value: 'CVE-2024-5678' }], + severity: 'high', }, security_vulnerability: null, dependency: { manifest_path: 'package.json' }, @@ -187,4 +194,40 @@ describe('modules/platform/github/schema', () => { 'Vulnerability Alert: Failed to parse some alerts', ); }); + + it('should parse severity and cvss_severities fields', () => { + const result = GithubVulnerabilityAlerts.parse([ + { + dismissed_reason: null, + security_advisory: { + description: 'Test advisory', + identifiers: [{ type: 'CVE', value: 'CVE-2024-1234' }], + severity: 'high', + cvss_severities: { + cvss_v3: { + vector_string: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H', + score: 9.8, + }, + cvss_v4: null, + }, + }, + security_vulnerability: { + first_patched_version: { identifier: '2.0.0' }, + package: { ecosystem: 'npm', name: 'test-package' }, + severity: 'critical', + vulnerable_version_range: '< 2.0.0', + }, + dependency: { manifest_path: 'package.json' }, + }, + ]); + + expect(result).toHaveLength(1); + expect(result[0].security_advisory.severity).toBe('high'); + expect(result[0].security_vulnerability?.severity).toBe('critical'); + expect(result[0].security_advisory.cvss_severities?.cvss_v3).toEqual({ + vector_string: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H', + score: 9.8, + }); + expect(result[0].security_advisory.cvss_severities?.cvss_v4).toBeNull(); + }); }); diff --git a/lib/modules/platform/github/schema.ts b/lib/modules/platform/github/schema.ts index ea54976d402..a7a0544fe95 100644 --- a/lib/modules/platform/github/schema.ts +++ b/lib/modules/platform/github/schema.ts @@ -26,14 +26,22 @@ const Package = z.object({ name: z.string(), }); +const Severity = z.enum(['low', 'medium', 'high', 'critical']); + const SecurityVulnerability = z .object({ first_patched_version: z.object({ identifier: z.string() }).nullish(), package: Package, + severity: Severity, vulnerable_version_range: z.string(), }) .nullable(); +const CvssSeverity = z.object({ + vector_string: z.string().nullable(), + score: z.number().nullable(), +}); + const SecurityAdvisory = z.object({ description: z.string(), identifiers: z.array( @@ -43,6 +51,13 @@ const SecurityAdvisory = z.object({ }), ), references: z.array(z.object({ url: z.string() })).optional(), + severity: Severity, + cvss_severities: z + .object({ + cvss_v3: CvssSeverity.nullish(), + cvss_v4: CvssSeverity.nullish(), + }) + .nullish(), }); export type SecurityAdvisory = z.infer; diff --git a/lib/workers/repository/init/vulnerability.spec.ts b/lib/workers/repository/init/vulnerability.spec.ts index 622ce26f20e..5d8188b5118 100644 --- a/lib/workers/repository/init/vulnerability.spec.ts +++ b/lib/workers/repository/init/vulnerability.spec.ts @@ -73,11 +73,13 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2018-15685' }, ], + severity: 'high', }, security_vulnerability: { package: { name: 'electron', ecosystem: 'npm' }, first_patched_version: { identifier: '1.8.8' }, vulnerable_version_range: '>= 1.8.0, < 1.8.8', + severity: 'high', }, }, ]); @@ -101,10 +103,12 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2016-3096' }, ], + severity: 'high', }, security_vulnerability: { package: { name: 'ansible', ecosystem: 'pip' }, vulnerable_version_range: '< 1.9.6.1', + severity: 'high', }, }, { @@ -122,11 +126,13 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2016-3096' }, ], + severity: 'high', }, security_vulnerability: { package: { name: 'ansible', ecosystem: 'pip' }, vulnerable_version_range: '< 1.9.6.1', first_patched_version: null, + severity: 'high', }, }, ]); @@ -148,11 +154,13 @@ describe('workers/repository/init/vulnerability', () => { description: 'go', identifiers: [{ type: 'GHSA', value: 'abc' }], references: [{ url: '' }], + severity: 'medium', }, security_vulnerability: { package: { name: 'foo', ecosystem: 'go' }, first_patched_version: { identifier: '1.8.3' }, vulnerable_version_range: '>= 1.8, < 1.8.3', + severity: 'medium', }, }, ]); @@ -173,11 +181,13 @@ describe('workers/repository/init/vulnerability', () => { security_advisory: { description: 'go', identifiers: [{ type: 'GHSA', value: 'abc' }], + severity: 'high', }, security_vulnerability: { package: { name: 'foo', ecosystem: 'go' }, first_patched_version: { identifier: '1.8.3' }, vulnerable_version_range: '>= 1.8, < 1.8.3', + severity: 'high', }, }, ]); @@ -191,7 +201,11 @@ describe('workers/repository/init/vulnerability', () => { matchFileNames: ['go.mod'], matchCurrentVersion: '< 1.8.3', vulnerabilityFixVersion: '1.8.3', - prBodyNotes: ['### GitHub Vulnerability Alerts', '#### abc\n\ngo'], + vulnerabilitySeverity: 'HIGH', + prBodyNotes: [ + '### GitHub Vulnerability Alerts', + '#### abc\n\ngo\n\n##### Severity\nHigh', + ], isVulnerabilityAlert: true, force: { enabled: true, @@ -218,6 +232,17 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2018-12022' }, ], + severity: 'high', + cvss_severities: { + cvss_v3: { + vector_string: 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H', + score: 8.1, + }, + cvss_v4: { + vector_string: null, + score: 0, + }, + }, }, security_vulnerability: { package: { @@ -226,6 +251,7 @@ describe('workers/repository/init/vulnerability', () => { }, first_patched_version: { identifier: '2.7.9.4' }, vulnerable_version_range: '< 2.7.9.4', + severity: 'high', }, }, ]); @@ -240,7 +266,7 @@ describe('workers/repository/init/vulnerability', () => { vulnerabilityFixVersion: '2.7.9.4', prBodyNotes: [ '### GitHub Vulnerability Alerts', - '#### [CVE-2018-12022](https://nvd.nist.gov/vuln/detail/CVE-2018-12022)\n\nAn 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.', + '#### [CVE-2018-12022](https://nvd.nist.gov/vuln/detail/CVE-2018-12022)\n\nAn 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.\n\n##### Severity\n- CVSS Score: 8.1 / 10 (High)\n- Vector String: `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H`', ], isVulnerabilityAlert: true, force: { @@ -268,6 +294,18 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2017-11770' }, ], + severity: 'high', + cvss_severities: { + cvss_v3: { + vector_string: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H', + score: 7.5, + }, + cvss_v4: { + vector_string: + 'CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N', + score: 8.7, + }, + }, }, security_vulnerability: { package: { @@ -276,6 +314,7 @@ describe('workers/repository/init/vulnerability', () => { }, first_patched_version: { identifier: '2.0.3' }, vulnerable_version_range: '>= 1.0.0, < 2.0.3', + severity: 'high', }, }, ]); @@ -290,7 +329,7 @@ describe('workers/repository/init/vulnerability', () => { vulnerabilityFixVersion: '2.0.3', prBodyNotes: [ '### GitHub Vulnerability Alerts', - '#### [CVE-2017-11770](https://nvd.nist.gov/vuln/detail/CVE-2017-11770)\n\n.NET Core 1.0, 1.1, and 2.0 allow an unauthenticated attacker to remotely cause a denial of service attack against a .NET Core web application by improperly parsing certificate data. A denial of service vulnerability exists when .NET Core improperly handles parsing certificate data, aka ".NET CORE Denial Of Service Vulnerability".', + '#### [CVE-2017-11770](https://nvd.nist.gov/vuln/detail/CVE-2017-11770)\n\n.NET Core 1.0, 1.1, and 2.0 allow an unauthenticated attacker to remotely cause a denial of service attack against a .NET Core web application by improperly parsing certificate data. A denial of service vulnerability exists when .NET Core improperly handles parsing certificate data, aka ".NET CORE Denial Of Service Vulnerability".\n\n##### Severity\n- CVSS Score: 8.7 / 10 (High)\n- Vector String: `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N`', ], isVulnerabilityAlert: true, force: { @@ -318,11 +357,13 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2017-7481' }, ], + severity: 'critical', }, security_vulnerability: { package: { name: 'ansible', ecosystem: 'pip' }, first_patched_version: { identifier: 'abc-2.3.1.0' }, vulnerable_version_range: '< 2.3.1.0', + severity: 'critical', }, }, { @@ -340,11 +381,13 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2015-3908' }, ], + severity: 'medium', }, security_vulnerability: { package: { name: 'ansible', ecosystem: 'pip' }, first_patched_version: { identifier: '1.9.2' }, vulnerable_version_range: '< 1.9.2', + severity: 'medium', }, }, { @@ -362,11 +405,13 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2016-8647' }, ], + severity: 'medium', }, security_vulnerability: { package: { name: 'ansible', ecosystem: 'pip' }, first_patched_version: { identifier: '2.2.1.0' }, vulnerable_version_range: '< 2.2.1.0', + severity: 'medium', }, }, { @@ -384,11 +429,13 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2016-8614' }, ], + severity: 'high', }, security_vulnerability: { package: { name: 'ansible', ecosystem: 'pip' }, first_patched_version: { identifier: '2.2.0' }, vulnerable_version_range: '< 2.2.0', + severity: 'high', }, }, { @@ -406,11 +453,13 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2016-8628' }, ], + severity: 'critical', }, security_vulnerability: { package: { name: 'ansible', ecosystem: 'pip' }, first_patched_version: { identifier: '2.2.0' }, vulnerable_version_range: '< 2.2.0', + severity: 'critical', }, }, { @@ -428,11 +477,13 @@ describe('workers/repository/init/vulnerability', () => { references: [ { url: 'https://nvd.nist.gov/vuln/detail/CVE-2016-9587' }, ], + severity: 'critical', }, security_vulnerability: { package: { name: 'ansible', ecosystem: 'pip' }, first_patched_version: { identifier: '2.1.4' }, vulnerable_version_range: '< 2.1.4', + severity: 'critical', }, }, ]); @@ -450,12 +501,12 @@ describe('workers/repository/init/vulnerability', () => { vulnerabilityFixVersion: '2.2.1.0', prBodyNotes: [ '### GitHub Vulnerability Alerts', - "#### [CVE-2017-7481](https://nvd.nist.gov/vuln/detail/CVE-2017-7481)\n\nAnsible before versions 2.3.1.0 and 2.4.0.0 fails to properly mark lookup-plugin results as unsafe. If an attacker could control the results of lookup() calls, they could inject Unicode strings to be parsed by the jinja2 templating system, resulting in code execution. By default, the jinja2 templating language is now marked as 'unsafe' and is not evaluated.", - "#### [CVE-2015-3908](https://nvd.nist.gov/vuln/detail/CVE-2015-3908)\n\nAnsible before 1.9.2 does not verify that the server hostname matches a domain name in the subject's Common Name (CN) or subjectAltName field of the X.509 certificate, which allows man-in-the-middle attackers to spoof SSL servers via an arbitrary valid certificate.", - "#### [CVE-2016-8647](https://nvd.nist.gov/vuln/detail/CVE-2016-8647)\n\nAn input validation vulnerability was found in Ansible's mysql_user module before 2.2.1.0, which may fail to correctly change a password in certain circumstances. Thus the previous password would still be active when it should have been changed.", - '#### [CVE-2016-8614](https://nvd.nist.gov/vuln/detail/CVE-2016-8614)\n\nA flaw was found in Ansible before version 2.2.0. The apt_key module does not properly verify key fingerprints, allowing remote adversary to create an OpenPGP key which matches the short key ID and inject this key instead of the correct key.', - '#### [CVE-2016-8628](https://nvd.nist.gov/vuln/detail/CVE-2016-8628)\n\nAnsible before version 2.2.0 fails to properly sanitize fact variables sent from the Ansible controller. An attacker with the ability to create special variables on the controller could execute arbitrary commands on Ansible clients as the user Ansible runs as.', - "#### [CVE-2016-9587](https://nvd.nist.gov/vuln/detail/CVE-2016-9587)\n\nAnsible 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.", + "#### [CVE-2017-7481](https://nvd.nist.gov/vuln/detail/CVE-2017-7481)\n\nAnsible before versions 2.3.1.0 and 2.4.0.0 fails to properly mark lookup-plugin results as unsafe. If an attacker could control the results of lookup() calls, they could inject Unicode strings to be parsed by the jinja2 templating system, resulting in code execution. By default, the jinja2 templating language is now marked as 'unsafe' and is not evaluated.\n\n##### Severity\nCritical", + "#### [CVE-2015-3908](https://nvd.nist.gov/vuln/detail/CVE-2015-3908)\n\nAnsible before 1.9.2 does not verify that the server hostname matches a domain name in the subject's Common Name (CN) or subjectAltName field of the X.509 certificate, which allows man-in-the-middle attackers to spoof SSL servers via an arbitrary valid certificate.\n\n##### Severity\nMedium", + "#### [CVE-2016-8647](https://nvd.nist.gov/vuln/detail/CVE-2016-8647)\n\nAn input validation vulnerability was found in Ansible's mysql_user module before 2.2.1.0, which may fail to correctly change a password in certain circumstances. Thus the previous password would still be active when it should have been changed.\n\n##### Severity\nMedium", + '#### [CVE-2016-8614](https://nvd.nist.gov/vuln/detail/CVE-2016-8614)\n\nA flaw was found in Ansible before version 2.2.0. The apt_key module does not properly verify key fingerprints, allowing remote adversary to create an OpenPGP key which matches the short key ID and inject this key instead of the correct key.\n\n##### Severity\nHigh', + '#### [CVE-2016-8628](https://nvd.nist.gov/vuln/detail/CVE-2016-8628)\n\nAnsible before version 2.2.0 fails to properly sanitize fact variables sent from the Ansible controller. An attacker with the ability to create special variables on the controller could execute arbitrary commands on Ansible clients as the user Ansible runs as.\n\n##### Severity\nCritical', + "#### [CVE-2016-9587](https://nvd.nist.gov/vuln/detail/CVE-2016-9587)\n\nAnsible 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.\n\n##### Severity\nCritical", ], isVulnerabilityAlert: true, force: { @@ -485,6 +536,7 @@ describe('workers/repository/init/vulnerability', () => { }, { url: 'https://github.com/advisories/GHSA-pwf7-47c3-mfhx' }, ], + severity: 'high', }, security_vulnerability: { package: { @@ -493,6 +545,7 @@ describe('workers/repository/init/vulnerability', () => { }, vulnerable_version_range: '<= 1.0.5', first_patched_version: { identifier: '1.0.6' }, + severity: 'high', }, }, ]); @@ -508,7 +561,7 @@ describe('workers/repository/init/vulnerability', () => { vulnerabilityFixVersion: '1.0.6', prBodyNotes: [ '### GitHub Vulnerability Alerts', - '#### [GHSA-pwf7-47c3-mfhx](https://github.com/j178/prek-action/security/advisories/GHSA-pwf7-47c3-mfhx)\n\n### Summary\\nThere are three potential attacks of arbitrary code injection vulnerability in the composite action at _action.yml_.\\n\\n### Details\\nThe GitHub Action variables `inputs.prek-version`, `inputs.extra_args`, and `inputs.extra-args` can be used to execute arbitrary code in the context of the action.', + '#### [GHSA-pwf7-47c3-mfhx](https://github.com/j178/prek-action/security/advisories/GHSA-pwf7-47c3-mfhx)\n\n### Summary\\nThere are three potential attacks of arbitrary code injection vulnerability in the composite action at _action.yml_.\\n\\n### Details\\nThe GitHub Action variables `inputs.prek-version`, `inputs.extra_args`, and `inputs.extra-args` can be used to execute arbitrary code in the context of the action.\n\n##### Severity\nHigh', ], isVulnerabilityAlert: true, force: { @@ -530,11 +583,13 @@ describe('workers/repository/init/vulnerability', () => { description: 'First vulnerability', identifiers: [{ type: 'GHSA', value: 'GHSA-xxxx-xxxx-xxx1' }], references: [{ url: 'https://example.com/vuln1' }], + severity: 'medium', }, security_vulnerability: { package: { name: 'vulnerable-pkg', ecosystem: 'npm' }, first_patched_version: { identifier: 'invalid-version-1' }, vulnerable_version_range: '< 1.0.0', + severity: 'medium', }, }, { @@ -546,11 +601,13 @@ describe('workers/repository/init/vulnerability', () => { description: 'Second vulnerability', identifiers: [{ type: 'GHSA', value: 'GHSA-xxxx-xxxx-xxx2' }], references: [{ url: 'https://example.com/vuln2' }], + severity: 'high', }, security_vulnerability: { package: { name: 'vulnerable-pkg', ecosystem: 'npm' }, first_patched_version: { identifier: 'also-invalid' }, vulnerable_version_range: '< 1.0.0', + severity: 'high', }, }, ]); @@ -563,5 +620,58 @@ describe('workers/repository/init/vulnerability', () => { ); expect(res.packageRules).toBeEmpty(); }); + + it('computes highest severity across multiple alerts for the same dependency', async () => { + platform.getVulnerabilityAlerts.mockResolvedValue([ + { + dismissed_reason: null, + dependency: { + manifest_path: 'package.json', + }, + security_advisory: { + description: 'Low severity issue', + identifiers: [{ type: 'CVE', value: 'CVE-2024-0001' }], + references: [{ url: 'https://example.com/vuln1' }], + severity: 'low', + }, + security_vulnerability: { + package: { name: 'some-pkg', ecosystem: 'npm' }, + first_patched_version: { identifier: '1.0.1' }, + vulnerable_version_range: '< 1.0.1', + severity: 'low', + }, + }, + { + dismissed_reason: null, + dependency: { + manifest_path: 'package.json', + }, + security_advisory: { + description: 'Critical severity issue', + identifiers: [{ type: 'CVE', value: 'CVE-2024-0002' }], + references: [{ url: 'https://example.com/vuln2' }], + severity: 'critical', + }, + security_vulnerability: { + package: { name: 'some-pkg', ecosystem: 'npm' }, + first_patched_version: { identifier: '1.0.2' }, + vulnerable_version_range: '< 1.0.2', + severity: 'critical', + }, + }, + ]); + + const res = await detectVulnerabilityAlerts(config); + expect(res).toMatchObject({ + packageRules: [ + { + matchPackageNames: ['some-pkg'], + vulnerabilityFixVersion: '1.0.2', + vulnerabilitySeverity: 'CRITICAL', + isVulnerabilityAlert: true, + }, + ], + }); + }); }); }); diff --git a/lib/workers/repository/init/vulnerability.ts b/lib/workers/repository/init/vulnerability.ts index 145459e9170..f04a325aca5 100644 --- a/lib/workers/repository/init/vulnerability.ts +++ b/lib/workers/repository/init/vulnerability.ts @@ -1,3 +1,4 @@ +import is from '@sindresorhus/is'; import type { PackageRule, RenovateConfig } from '../../../config/types.ts'; import { NO_VULNERABILITY_ALERTS } from '../../../constants/error-messages.ts'; import { logger } from '../../../logger/index.ts'; @@ -16,8 +17,12 @@ import * as rubyVersioning from '../../../modules/versioning/ruby/index.ts'; import * as semverVersioning from '../../../modules/versioning/semver/index.ts'; import { sanitizeMarkdown } from '../../../util/markdown.ts'; import { escapeRegExp } from '../../../util/regex.ts'; +import { titleCase } from '../../../util/string.ts'; import { githubEcosystemToDatasource } from '../../../util/vulnerability/ecosystem.ts'; -import { getFixedVersionConstraint } from '../../../util/vulnerability/utils.ts'; +import { + getFixedVersionConstraint, + getHighestVulnerabilitySeverity, +} from '../../../util/vulnerability/utils.ts'; type Datasource = string; type DependencyName = string; @@ -33,6 +38,7 @@ type CombinedAlert = Record< advisories: SecurityAdvisory[]; fileType?: string; firstPatchedVersion?: string; + severity?: string; } > > @@ -106,6 +112,10 @@ export async function detectVulnerabilityAlerts( }; const alertDetails = combinedAlerts[fileName][datasource][depName]; alertDetails.advisories.push(advisory); + alertDetails.severity = getHighestVulnerabilitySeverity( + { vulnerabilitySeverity: alertDetails.severity }, + { vulnerabilitySeverity: alert.security_vulnerability.severity }, + ); const versioningApi = allVersioning.get(versionings[datasource]); if (versioningApi.isVersion(firstPatchedVersion)) { if ( @@ -157,6 +167,17 @@ export async function detectVulnerabilityAlerts( content += '\n\n'; content += sanitizeMarkdown(description); + + content += '\n\n##### Severity\n'; + const { cvss_v4, cvss_v3 } = advisory.cvss_severities ?? {}; + const cvss = cvss_v4?.vector_string ? cvss_v4 : cvss_v3; + if (is.number(cvss?.score) && cvss?.vector_string) { + content += `- CVSS Score: ${cvss.score.toFixed(1)} / 10 (${titleCase(advisory.severity)})\n`; + content += `- Vector String: \`${cvss.vector_string}\``; + } else { + content += titleCase(advisory.severity); + } + return content; }), ); @@ -189,6 +210,7 @@ export async function detectVulnerabilityAlerts( ...matchRule, matchCurrentVersion, vulnerabilityFixVersion: val.firstPatchedVersion, + vulnerabilitySeverity: val.severity, prBodyNotes, isVulnerabilityAlert: true, force: {