From b3891bf87459d438a751075a7a3a0ddcf776ede9 Mon Sep 17 00:00:00 2001 From: Michael Wager Date: Mon, 7 Jul 2025 15:05:15 +0200 Subject: [PATCH 1/6] fix(Kiuwan Sca Scan): improve Kiuwan SCA parser to support multi-component findings --- dojo/tools/kiuwan_sca/parser.py | 96 +++++++++++++++++---------------- 1 file changed, 51 insertions(+), 45 deletions(-) diff --git a/dojo/tools/kiuwan_sca/parser.py b/dojo/tools/kiuwan_sca/parser.py index ca74e41cac3..ea77fbc8ff3 100644 --- a/dojo/tools/kiuwan_sca/parser.py +++ b/dojo/tools/kiuwan_sca/parser.py @@ -1,8 +1,11 @@ import hashlib import json +import logging from dojo.models import Finding +logger = logging.getLogger(__name__) + __author__ = "mwager" @@ -37,50 +40,53 @@ def get_findings(self, filename, test): if row["muted"] is True: continue - finding = Finding(test=test) - finding.unique_id_from_tool = row["id"] - finding.cve = row["cve"] - finding.description = row["description"] - finding.severity = self.SEVERITY[row["securityRisk"]] - - if "components" in row and len(row["components"]) > 0: - finding.component_name = row["components"][0]["artifact"] - finding.component_version = row["components"][0]["version"] - finding.title = finding.component_name + " v" + str(finding.component_version) - - if not finding.title: - finding.title = row["cve"] - - if "cwe" in row and "CWE-" in row["cwe"]: - finding.cwe = int(row["cwe"].replace("CWE-", "")) - - if "epss_score" in row: - finding.epss_score = row["epss_score"] - if "epss_percentile" in row: - finding.epss_percentile = row["epss_percentile"] - - if "cVSSv3BaseScore" in row: - finding.cvssv3_score = float(row["cVSSv3BaseScore"]) - - finding.references = "See Kiuwan Web UI" - finding.mitigation = "See Kiuwan Web UI" - finding.static_finding = True - - key = hashlib.sha256( - ( - finding.description - + "|" - + finding.severity - + "|" - + finding.component_name - + "|" - + finding.component_version - + "|" - + str(finding.cwe) - ).encode("utf-8"), - ).hexdigest() - - if key not in dupes: - dupes[key] = finding + components = row.get("components", []) + if not components: + logger.warning("WARNING: Insights Finding from kiuwan does not have a related component - Skipping.") + continue + + # We want one unique finding in DD for each component affected: + for component in components: + finding = Finding(test=test) + finding.unique_id_from_tool = row["id"] + finding.cve = row["cve"] + finding.description = row["description"] + finding.severity = self.SEVERITY[row["securityRisk"]] + + finding.component_name = component.get("artifact", "Unknown") + finding.component_version = component.get("version", "Unknown") + finding.title = f"{finding.component_name} v{finding.component_version}" + + if "cwe" in row and "CWE-" in row["cwe"]: + finding.cwe = int(row["cwe"].replace("CWE-", "")) + + if "epss_score" in row: + finding.epss_score = row["epss_score"] + if "epss_percentile" in row: + finding.epss_percentile = row["epss_percentile"] + + if "cVSSv3BaseScore" in row: + finding.cvssv3_score = float(row["cVSSv3BaseScore"]) + + finding.references = "See Kiuwan Web UI" + finding.mitigation = "See Kiuwan Web UI" + finding.static_finding = True + + key = hashlib.sha256( + ( + finding.description + + "|" + + finding.severity + + "|" + + finding.component_name + + "|" + + finding.component_version + + "|" + + str(finding.cwe) + ).encode("utf-8"), + ).hexdigest() + + if key not in dupes: + dupes[key] = finding return list(dupes.values()) From 3d1f462d55e8b56d6a0f819aeaea43d8d7b369a3 Mon Sep 17 00:00:00 2001 From: Michael Wager Date: Tue, 8 Jul 2025 15:16:52 +0200 Subject: [PATCH 2/6] test: update unit tests according to correct logic --- unittests/tools/test_kiuwan_sca_parser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unittests/tools/test_kiuwan_sca_parser.py b/unittests/tools/test_kiuwan_sca_parser.py index 7653f6eb4aa..8438e277bd2 100644 --- a/unittests/tools/test_kiuwan_sca_parser.py +++ b/unittests/tools/test_kiuwan_sca_parser.py @@ -15,15 +15,15 @@ def test_parse_file_with_two_vuln_has_two_findings(self): with (get_unit_tests_scans_path("kiuwan_sca") / "kiuwan_sca_two_vuln.json").open(encoding="utf-8") as testfile: parser = KiuwanSCAParser() findings = parser.get_findings(testfile, Test()) - # file contains 3, but we only get 2 as "muted" ones are ignored: - self.assertEqual(2, len(findings)) + # file contains 3 cves, one of them is muted. so we have 2 cves with a total of 5 components + self.assertEqual(5, len(findings)) def test_parse_file_with_multiple_vuln_has_multiple_finding(self): with (get_unit_tests_scans_path("kiuwan_sca") / "kiuwan_sca_many_vuln.json").open(encoding="utf-8") as testfile: parser = KiuwanSCAParser() findings = parser.get_findings(testfile, Test()) - # also tests deduplication as there are 28 findings in the file: - self.assertEqual(27, len(findings)) + # also tests deduplication as there are 28 cves in the file (but some including >1 components!): + self.assertEqual(45, len(findings)) def test_correct_mapping(self): with (get_unit_tests_scans_path("kiuwan_sca") / "kiuwan_sca_two_vuln.json").open(encoding="utf-8") as testfile: From 52e54189b1b16b7a946dc17ea4b61884c0079a23 Mon Sep 17 00:00:00 2001 From: Michael Wager Date: Fri, 11 Jul 2025 07:55:38 +0200 Subject: [PATCH 3/6] refactor: use better unique id from tool --- dojo/tools/kiuwan_sca/parser.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dojo/tools/kiuwan_sca/parser.py b/dojo/tools/kiuwan_sca/parser.py index ea77fbc8ff3..9b96d195b19 100644 --- a/dojo/tools/kiuwan_sca/parser.py +++ b/dojo/tools/kiuwan_sca/parser.py @@ -48,7 +48,7 @@ def get_findings(self, filename, test): # We want one unique finding in DD for each component affected: for component in components: finding = Finding(test=test) - finding.unique_id_from_tool = row["id"] + finding.unique_id_from_tool = f"{row['cve']}|{component.get('artifact')}|{component.get('version')}" finding.cve = row["cve"] finding.description = row["description"] finding.severity = self.SEVERITY[row["securityRisk"]] @@ -74,7 +74,11 @@ def get_findings(self, filename, test): key = hashlib.sha256( ( - finding.description + str(finding.unique_id_from_tool) + + "|" + + finding.cve + + "|" + + finding.description + "|" + finding.severity + "|" @@ -82,7 +86,7 @@ def get_findings(self, filename, test): + "|" + finding.component_version + "|" - + str(finding.cwe) + + str(finding.cwe or "") ).encode("utf-8"), ).hexdigest() From db2ca5ee3e9fd6631efa0fb65921e43254769685 Mon Sep 17 00:00:00 2001 From: Michael Wager Date: Fri, 11 Jul 2025 11:23:43 +0200 Subject: [PATCH 4/6] fix unit tests --- unittests/tools/test_kiuwan_sca_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittests/tools/test_kiuwan_sca_parser.py b/unittests/tools/test_kiuwan_sca_parser.py index 8438e277bd2..2febf3d7423 100644 --- a/unittests/tools/test_kiuwan_sca_parser.py +++ b/unittests/tools/test_kiuwan_sca_parser.py @@ -37,7 +37,7 @@ def test_correct_mapping(self): self.assertEqual(finding1.component_name, "org.apache.cxf:cxf-rt-ws-policy") self.assertEqual(finding1.component_version, "3.3.5") self.assertEqual(finding1.cwe, 835) - self.assertEqual(finding1.unique_id_from_tool, 158713) + self.assertEqual(finding1.unique_id_from_tool, "CVE-2021-30468|org.apache.cxf:cxf-rt-ws-policy|3.3.5") self.assertEqual(finding1.cvssv3_score, 7.5) self.assertEqual(finding1.epss_score, 0.1) self.assertEqual(finding1.epss_percentile, 0.2) From daa80149a03b5618104f50b046f5fd000c4fe16b Mon Sep 17 00:00:00 2001 From: Michael Wager Date: Mon, 14 Jul 2025 08:51:58 +0200 Subject: [PATCH 5/6] refactor: back to using unique id from tool like supposed to be --- dojo/tools/kiuwan_sca/parser.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dojo/tools/kiuwan_sca/parser.py b/dojo/tools/kiuwan_sca/parser.py index 9b96d195b19..14c47416085 100644 --- a/dojo/tools/kiuwan_sca/parser.py +++ b/dojo/tools/kiuwan_sca/parser.py @@ -48,7 +48,7 @@ def get_findings(self, filename, test): # We want one unique finding in DD for each component affected: for component in components: finding = Finding(test=test) - finding.unique_id_from_tool = f"{row['cve']}|{component.get('artifact')}|{component.get('version')}" + finding.unique_id_from_tool = str(row['id']) finding.cve = row["cve"] finding.description = row["description"] finding.severity = self.SEVERITY[row["securityRisk"]] @@ -74,11 +74,7 @@ def get_findings(self, filename, test): key = hashlib.sha256( ( - str(finding.unique_id_from_tool) - + "|" - + finding.cve - + "|" - + finding.description + finding.description + "|" + finding.severity + "|" From 1b90dc03b9a7be883ace1fefa57207cf8d422ca5 Mon Sep 17 00:00:00 2001 From: Michael Wager Date: Mon, 14 Jul 2025 08:55:13 +0200 Subject: [PATCH 6/6] fix: lint --- dojo/tools/kiuwan_sca/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dojo/tools/kiuwan_sca/parser.py b/dojo/tools/kiuwan_sca/parser.py index 14c47416085..645dadb7cfa 100644 --- a/dojo/tools/kiuwan_sca/parser.py +++ b/dojo/tools/kiuwan_sca/parser.py @@ -48,7 +48,7 @@ def get_findings(self, filename, test): # We want one unique finding in DD for each component affected: for component in components: finding = Finding(test=test) - finding.unique_id_from_tool = str(row['id']) + finding.unique_id_from_tool = str(row["id"]) finding.cve = row["cve"] finding.description = row["description"] finding.severity = self.SEVERITY[row["securityRisk"]]