diff --git a/dojo/tools/aqua/parser.py b/dojo/tools/aqua/parser.py index 2ae981f4e8b..cfe90e93928 100644 --- a/dojo/tools/aqua/parser.py +++ b/dojo/tools/aqua/parser.py @@ -115,32 +115,32 @@ def get_findings(self, json_output, test): elif "result" in tree: # Aqua Scan Report from apiv2 resulttree = tree["result"] for vuln in resulttree: - resource = vuln.get("resource") + resource = vuln.get("resource") or {} item = self.get_item(resource, vuln, test) - unique_key = resource.get("cpe") + vuln.get("name", "None") + resource.get("path", "None") + unique_key = (resource.get("cpe") or "") + (vuln.get("name") or "") + (resource.get("path") or "") self.items[unique_key] = item elif "cves" in tree: # Aqua Scan Report from apiv1 for cve in tree["cves"]: - unique_key = cve.get("file") + cve.get("name") + unique_key = (cve.get("file") or "") + (cve.get("name") or "") self.items[unique_key] = self.get_item_v2(cve, test) return list(self.items.values()) def vulnerability_tree(self, vulnerabilitytree, test): for node in vulnerabilitytree: - resource = node.get("resource") + resource = node.get("resource") or {} vulnerabilities = node.get("vulnerabilities", []) sensitive_items = resource.get("sensitive_items", []) if vulnerabilities is None: vulnerabilities = [] for vuln in vulnerabilities: item = self.get_item(resource, vuln, test) - unique_key = resource.get("cpe") + vuln.get("name", "None") + resource.get("path", "None") + unique_key = (resource.get("cpe") or "") + (vuln.get("name") or "") + (resource.get("path") or "") self.items[unique_key] = item if sensitive_items is None: sensitive_items = [] for sensitive_item in sensitive_items: item = self.get_item_sensitive_data(resource, sensitive_item, test) - unique_key = resource.get("cpe") + resource.get("path", "None") + str(sensitive_item) + unique_key = (resource.get("cpe") or "") + (resource.get("path") or "") + str(sensitive_item) self.items[unique_key] = item def get_item(self, resource, vuln, test): @@ -297,6 +297,8 @@ def get_item_sensitive_data(self, resource, sensitive_item, test): return finding def severity_of(self, score): + if score is None: + return "Info" if isinstance(score, str): if score == "high": return "High" diff --git a/dojo/tools/snyk/parser.py b/dojo/tools/snyk/parser.py index d0b1e056ce6..3d541c21a62 100644 --- a/dojo/tools/snyk/parser.py +++ b/dojo/tools/snyk/parser.py @@ -246,21 +246,22 @@ def get_item(self, vulnerability, test, target_file=None, upgrades=None, image=N # manage CVE and CWE with idnitifiers cwe_references = "" - if "identifiers" in vulnerability: - if "CVE" in vulnerability["identifiers"]: - vulnerability_ids = vulnerability["identifiers"]["CVE"] + identifiers = vulnerability.get("identifiers") or {} + if identifiers: + if "CVE" in identifiers: + vulnerability_ids = identifiers["CVE"] if vulnerability_ids: finding.unsaved_vulnerability_ids = vulnerability_ids - if "CWE" in vulnerability["identifiers"]: - cwes = vulnerability["identifiers"]["CWE"] + if "CWE" in identifiers: + cwes = identifiers["CWE"] if cwes: # Per the current json format, if several CWEs, take the # first one. finding.cwe = int(cwes[0].split("-")[1]) # Persist the full list of CWEs via the Finding_CWE relation finding.unsaved_cwes = [int(c.split("-")[1]) for c in cwes] - if len(vulnerability["identifiers"]["CWE"]) > 1: + if len(identifiers["CWE"]) > 1: cwe_references = ", ".join(cwes) else: finding.cwe = 1035 diff --git a/unittests/scans/aqua/api_v1_missing_fields.json b/unittests/scans/aqua/api_v1_missing_fields.json new file mode 100644 index 00000000000..c0dbb2ca8cc --- /dev/null +++ b/unittests/scans/aqua/api_v1_missing_fields.json @@ -0,0 +1,16 @@ +{ + "cves": [ + { + "name": "CVE-2023-0003", + "score": 8.1, + "description": "Test finding with missing file field", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-0003" + }, + { + "file": "/usr/lib/libssl.so", + "name": null, + "score": 6.5, + "description": "Test finding with null name field" + } + ] +} diff --git a/unittests/scans/aqua/api_v2_missing_fields.json b/unittests/scans/aqua/api_v2_missing_fields.json new file mode 100644 index 00000000000..0d71d560622 --- /dev/null +++ b/unittests/scans/aqua/api_v2_missing_fields.json @@ -0,0 +1,23 @@ +{ + "result": [ + { + "name": "CVE-2023-0001", + "description": "Test vulnerability without resource object", + "nvd_severity": "high", + "nvd_cvss3_score": 7.5, + "resource": null + }, + { + "name": "CVE-2023-0002", + "description": "Test vulnerability with resource missing cpe", + "nvd_severity": "medium", + "nvd_cvss3_score": 5.0, + "resource": { + "format": "apk", + "name": "libcrypto", + "version": "1.0.2", + "path": "/usr/lib/libcrypto.so" + } + } + ] +} diff --git a/unittests/scans/aqua/cicd_missing_resource_fields.json b/unittests/scans/aqua/cicd_missing_resource_fields.json new file mode 100644 index 00000000000..9205b0670a2 --- /dev/null +++ b/unittests/scans/aqua/cicd_missing_resource_fields.json @@ -0,0 +1,28 @@ +{ + "resources": [ + { + "resource": null, + "vulnerabilities": [ + { + "name": "CVE-2023-0004", + "description": "Vuln on a node with null resource", + "nvd_score": 7.0 + } + ] + }, + { + "resource": { + "format": "apk", + "name": "busybox", + "version": "1.30.1" + }, + "vulnerabilities": [ + { + "name": "CVE-2023-0005", + "description": "Vuln on a resource missing cpe and path", + "nvd_score": 5.5 + } + ] + } + ] +} diff --git a/unittests/scans/snyk/single_project_null_identifiers.json b/unittests/scans/snyk/single_project_null_identifiers.json new file mode 100644 index 00000000000..acf1abaf83b --- /dev/null +++ b/unittests/scans/snyk/single_project_null_identifiers.json @@ -0,0 +1,41 @@ +{ + "vulnerabilities": [ + { + "id": "SNYK-TEST-0001", + "title": "Test Null Identifiers", + "packageName": "test-package", + "version": "1.0.0", + "severity": "high", + "cvssScore": 7.5, + "semver": { + "vulnerable": [ + "<2.0.0" + ] + }, + "from": [ + "my-project@1.0.0", + "test-package@1.0.0" + ], + "identifiers": null, + "description": "Finding with null identifiers field" + }, + { + "id": "SNYK-TEST-0002", + "title": "Test Empty Identifiers", + "packageName": "another-package", + "version": "3.2.1", + "severity": "medium", + "cvssScore": 5.0, + "semver": { + "vulnerable": ">=1.0.0 <4.0.0" + }, + "from": [ + "my-project@1.0.0", + "another-package@3.2.1" + ], + "identifiers": {}, + "description": "Finding with empty identifiers object" + } + ], + "packageManager": "npm" +} diff --git a/unittests/tools/test_aqua_parser.py b/unittests/tools/test_aqua_parser.py index d7141aa9475..f9a17d32153 100644 --- a/unittests/tools/test_aqua_parser.py +++ b/unittests/tools/test_aqua_parser.py @@ -139,3 +139,22 @@ def test_aqua_parser_over_api_v2_empty(self): parser = AquaParser() findings = parser.get_findings(testfile, Test()) self.assertEqual(0, len(findings)) + + def test_aqua_parser_api_v2_with_missing_fields(self): + with (get_unit_tests_scans_path("aqua") / "api_v2_missing_fields.json").open(encoding="utf-8") as testfile: + parser = AquaParser() + findings = parser.get_findings(testfile, Test()) + # First result has null resource, second is missing cpe + self.assertEqual(2, len(findings)) + + def test_aqua_parser_api_v1_with_missing_fields(self): + with (get_unit_tests_scans_path("aqua") / "api_v1_missing_fields.json").open(encoding="utf-8") as testfile: + parser = AquaParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(2, len(findings)) + + def test_aqua_parser_cicd_with_missing_resource_fields(self): + with (get_unit_tests_scans_path("aqua") / "cicd_missing_resource_fields.json").open(encoding="utf-8") as testfile: + parser = AquaParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(2, len(findings)) diff --git a/unittests/tools/test_snyk_parser.py b/unittests/tools/test_snyk_parser.py index 86f0fa68326..7fa49e45bf6 100644 --- a/unittests/tools/test_snyk_parser.py +++ b/unittests/tools/test_snyk_parser.py @@ -214,6 +214,16 @@ def test_snykcode_issue_9270_epss(self): findings[0].title, ) + def test_snykParser_null_identifiers(self): + with (get_unit_tests_scans_path("snyk") / "single_project_null_identifiers.json").open(encoding="utf-8") as testfile: + parser = SnykParser() + findings = parser.get_findings(testfile, Test()) + self.assertEqual(2, len(findings)) + # First finding has null identifiers — no CVE/CWE should be extracted + self.assertFalse(getattr(findings[0], "unsaved_vulnerability_ids", [])) + # Second finding has empty identifiers — same result + self.assertFalse(getattr(findings[1], "unsaved_vulnerability_ids", [])) + class TestSnykParserImageLocations(DojoTestCase): @skip_unless_v3