diff --git a/docs/content/supported_tools/parsers/file/generic.md b/docs/content/supported_tools/parsers/file/generic.md index 6905c0ceed1..569309c05c1 100644 --- a/docs/content/supported_tools/parsers/file/generic.md +++ b/docs/content/supported_tools/parsers/file/generic.md @@ -31,6 +31,7 @@ Generic Findings Import can be used to import any report in CSV or JSON format. - known_exploited: Indicator if the finding is listed in Known Exploited List. Must be TRUE, or FALSE - ransomware_used: Indicator if the finding is used in Ransomware. Must be TRUE, or FALSE - fix_available: Indicator if fix available for the finding. Must be TRUE, or FALSE +- fix_version: Version where fix is available. String value. - kev_date: Date the finding was added to Known Exploited Vulnerabilities list in mm/dd/yyyy format or ISO format. The CSV expects a header row with the names of the attributes. @@ -94,6 +95,7 @@ The list of supported fields in JSON format: - known_exploited: Bool - ransomware_used: Bool - fix_available: Bool +- fix_version: String ### Example JSON @@ -114,6 +116,7 @@ The list of supported fields in JSON format: "known_exploited": true, "ransomware_used": true, "fix_available": true, + "fix_version": "0.0.00", "kev_date": "2024-05-01", "file_path": "src/first.cpp", "line": 13, diff --git a/dojo/tools/generic/csv_parser.py b/dojo/tools/generic/csv_parser.py index d32c5b999d0..3ea366b5dcf 100644 --- a/dojo/tools/generic/csv_parser.py +++ b/dojo/tools/generic/csv_parser.py @@ -103,6 +103,9 @@ def _get_findings_csv(self, filename): if "fix_available" in row: finding.fix_available = bool(row["fix_available"]) + if "fix_version" in row: + finding.fix_version = row["fix_version"] + # manage endpoints if row.get("Url"): if settings.V3_FEATURE_LOCATIONS: diff --git a/dojo/tools/generic/json_parser.py b/dojo/tools/generic/json_parser.py index ad04b4e547b..30a67ba1b0e 100644 --- a/dojo/tools/generic/json_parser.py +++ b/dojo/tools/generic/json_parser.py @@ -110,6 +110,7 @@ def _get_test_json(self, data): "known_exploited", "ransomware_used", "fix_available", + "fix_version", }.union(required) not_allowed = sorted(set(item).difference(allowed)) if not_allowed: diff --git a/unittests/scans/generic/generic_report_fix_version.csv b/unittests/scans/generic/generic_report_fix_version.csv new file mode 100644 index 00000000000..e60d3000a97 --- /dev/null +++ b/unittests/scans/generic/generic_report_fix_version.csv @@ -0,0 +1,3 @@ +Date,Title,CweId,Url,Severity,Description,Mitigation,Impact,References,Active,Verified,fix_available,fix_version +01/15/2025,Test finding with fix_version,502,,High,Vulnerability with fix version available,Upgrade to 2.1.3,,,TRUE,FALSE,TRUE,2.1.3 +01/15/2025,Test finding without fix_version,611,,Medium,Vulnerability without fix version,,,,TRUE,FALSE,FALSE, \ No newline at end of file diff --git a/unittests/scans/generic/generic_report_fix_version.json b/unittests/scans/generic/generic_report_fix_version.json new file mode 100644 index 00000000000..874ec911923 --- /dev/null +++ b/unittests/scans/generic/generic_report_fix_version.json @@ -0,0 +1,25 @@ +{ + "findings": [ + { + "title": "test title with fix_version", + "description": "Vulnerability with fix version available", + "severity": "High", + "date": "2025-01-15", + "cve": "CVE-2025-12345", + "cwe": 502, + "fix_available": true, + "fix_version": "2.1.3", + "component_name": "spring-core", + "component_version": "6.1.0" + }, + { + "title": "test title without fix_version", + "description": "Vulnerability without fix version", + "severity": "Medium", + "date": "2025-01-15", + "cve": "CVE-2025-67890", + "cwe": 611, + "fix_available": false + } + ] +} \ No newline at end of file diff --git a/unittests/tools/test_generic_parser.py b/unittests/tools/test_generic_parser.py index 50de41b74a0..441c669ff62 100644 --- a/unittests/tools/test_generic_parser.py +++ b/unittests/tools/test_generic_parser.py @@ -684,3 +684,35 @@ def test_parse_json_custom_test_with_meta(self): self.assertEqual(test.description, "The contents of this report is from a tool that gathers vulnerabilities both statically and dynamically") self.assertEqual(test.dynamic_tool, True) self.assertEqual(test.static_tool, True) + + def test_parse_json_with_fix_version(self): + with (get_unit_tests_scans_path("generic") / "generic_report_fix_version.json").open(encoding="utf-8") as file: + parser = GenericParser() + findings = parser.get_findings(file, self.test) + self.validate_locations(findings) + self.assertEqual(2, len(findings)) + + finding = findings[0] + self.assertEqual("test title with fix_version", finding.title) + self.assertTrue(finding.fix_available) + self.assertEqual("2.1.3", finding.fix_version) + self.assertEqual("spring-core", finding.component_name) + + finding = findings[1] + self.assertEqual("test title without fix_version", finding.title) + self.assertFalse(finding.fix_available) + self.assertIsNone(finding.fix_version) + + def test_parse_csv_with_fix_version(self): + with (get_unit_tests_scans_path("generic") / "generic_report_fix_version.csv").open(encoding="utf-8") as file: + parser = GenericParser() + findings = parser.get_findings(file, self.test) + self.validate_locations(findings) + self.assertEqual(2, len(findings)) + + finding = findings[0] + self.assertEqual("Test finding with fix_version", finding.title) + self.assertEqual("2.1.3", finding.fix_version) + + finding = findings[1] + self.assertEqual("Test finding without fix_version", finding.title)