From 3f4a62c082bdb5ca1940bba75f4e56128e7e550b Mon Sep 17 00:00:00 2001 From: Jordi Sayeras Date: Tue, 2 Jun 2026 11:40:29 +0200 Subject: [PATCH] feat(parser): set fix_available on GitHub Vulnerability findings Populate fix_available from firstPatchedVersion in the GraphQL response. --- .../parsers/file/github_vulnerability.md | 2 + dojo/tools/github_vulnerability/parser.py | 6 ++ .../github-1-vuln-no-fix.json | 58 +++++++++++++++++++ .../tools/test_github_vulnerability_parser.py | 20 +++++++ 4 files changed, 86 insertions(+) create mode 100644 unittests/scans/github_vulnerability/github-1-vuln-no-fix.json diff --git a/docs/content/supported_tools/parsers/file/github_vulnerability.md b/docs/content/supported_tools/parsers/file/github_vulnerability.md index 5705165913a..db2a1ceb4c6 100644 --- a/docs/content/supported_tools/parsers/file/github_vulnerability.md +++ b/docs/content/supported_tools/parsers/file/github_vulnerability.md @@ -21,6 +21,8 @@ vulnerabilityAlerts (RepositoryVulnerabilityAlert object) + severity (CRITICAL/HIGH/LOW/MODERATE) + package (optional) + name (optional) + + firstPatchedVersion (optional, sets fix_available) + + identifier (optional) + advisory (SecurityAdvisory object) + description + summary diff --git a/dojo/tools/github_vulnerability/parser.py b/dojo/tools/github_vulnerability/parser.py index 5c646086aeb..4b9a53afed7 100644 --- a/dojo/tools/github_vulnerability/parser.py +++ b/dojo/tools/github_vulnerability/parser.py @@ -83,6 +83,12 @@ def get_findings(self, filename, test): pkg = vuln.get("package", {}) finding.component_name = pkg.get("name") + first_patched = vuln.get("firstPatchedVersion") + finding.fix_available = ( + first_patched is not None + and first_patched.get("identifier") is not None + ) + if alert.get("createdAt"): finding.date = dateutil.parser.parse(alert.get("createdAt")) if alert.get("state") in {"FIXED", "DISMISSED"}: diff --git a/unittests/scans/github_vulnerability/github-1-vuln-no-fix.json b/unittests/scans/github_vulnerability/github-1-vuln-no-fix.json new file mode 100644 index 00000000000..748bcad7336 --- /dev/null +++ b/unittests/scans/github_vulnerability/github-1-vuln-no-fix.json @@ -0,0 +1,58 @@ +{ + "data": { + "repository": { + "vulnerabilityAlerts": { + "nodes": [ + { + "id": "RVA_kwDOLJyUo88AAAABNoFixAv", + "createdAt": "2024-03-15T10:00:00Z", + "vulnerableManifestPath": "app/package.json", + "dependabotUpdate": null, + "securityVulnerability": { + "severity": "HIGH", + "updatedAt": "2024-03-10T12:00:00Z", + "package": { + "name": "example-package", + "ecosystem": "NPM" + }, + "firstPatchedVersion": null, + "vulnerableVersionRange": "<= 2.0.0", + "advisory": { + "description": "Example vulnerability with no fix available yet.", + "summary": "Prototype pollution in example-package", + "identifiers": [ + { + "value": "GHSA-test-no-fix", + "type": "GHSA" + }, + { + "value": "CVE-2024-99999", + "type": "CVE" + } + ], + "references": [ + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-99999" + } + ], + "cvss": { + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N" + } + } + }, + "state": "OPEN", + "vulnerableManifestFilename": "package.json", + "vulnerableRequirements": "= 1.5.0", + "number": 42, + "dependencyScope": "RUNTIME", + "dismissComment": null, + "dismissReason": null, + "dismissedAt": null, + "fixedAt": null + } + ] + }, + "url": "https://github.com/example-org/example-repo" + } + } +} diff --git a/unittests/tools/test_github_vulnerability_parser.py b/unittests/tools/test_github_vulnerability_parser.py index 2e5869476bf..803ac8a5f02 100644 --- a/unittests/tools/test_github_vulnerability_parser.py +++ b/unittests/tools/test_github_vulnerability_parser.py @@ -313,6 +313,26 @@ def test_parser_version(self): self.assertEqual(finding.component_version, "5.3.29") self.assertAlmostEqual(finding.epss_score, 0.00212, places=5) self.assertAlmostEqual(finding.epss_percentile, 0.44035, places=5) + self.assertTrue(finding.fix_available) + + def test_parse_no_fix_available(self): + """Finding with null firstPatchedVersion should have fix_available=False""" + with (get_unit_tests_scans_path("github_vulnerability") / "github-1-vuln-no-fix.json").open( + encoding="utf-8", + ) as testfile: + parser = GithubVulnerabilityParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(1, len(findings)) + for finding in findings: + finding.clean() + + with self.subTest(i=0): + finding = findings[0] + self.assertEqual(finding.title, "Prototype pollution in example-package") + self.assertEqual(finding.severity, "High") + self.assertEqual(finding.component_name, "example-package") + self.assertEqual(finding.component_version, "1.5.0") + self.assertFalse(finding.fix_available) def test_parse_file_issue_9582(self): with (get_unit_tests_scans_path("github_vulnerability") / "issue_9582.json").open(encoding="utf-8") as testfile: