From 6a01653c092e300021dcf6930e1ba85f4f13efda Mon Sep 17 00:00:00 2001 From: Jaimin2687 Date: Mon, 21 Sep 2026 19:07:23 +0530 Subject: [PATCH 1/2] Fix crash bugs in Aqua and Snyk parsers caused by None field concatenation The Aqua parser crashes with TypeError when scan reports contain null or missing resource objects, or when fields like cpe, file, or name are absent. This affects all three report format variants (apiv1, apiv2, and CICD). The Snyk parser crashes with TypeError when the identifiers field is present in the JSON report but set to null instead of a dict. Guard all affected code paths with inline fallbacks so that missing or null fields produce empty strings instead of raising exceptions. --- dojo/tools/aqua/parser.py | 12 +++--- dojo/tools/snyk/parser.py | 13 +++--- .../scans/aqua/api_v1_missing_fields.json | 16 ++++++++ .../scans/aqua/api_v2_missing_fields.json | 23 +++++++++++ .../aqua/cicd_missing_resource_fields.json | 28 +++++++++++++ .../snyk/single_project_null_identifiers.json | 41 +++++++++++++++++++ unittests/tools/test_aqua_parser.py | 19 +++++++++ unittests/tools/test_snyk_parser.py | 10 +++++ 8 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 unittests/scans/aqua/api_v1_missing_fields.json create mode 100644 unittests/scans/aqua/api_v2_missing_fields.json create mode 100644 unittests/scans/aqua/cicd_missing_resource_fields.json create mode 100644 unittests/scans/snyk/single_project_null_identifiers.json diff --git a/dojo/tools/aqua/parser.py b/dojo/tools/aqua/parser.py index 2ae981f4e8b..fbc01c44fc6 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): 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..4c2e48e22dd 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 set + self.assertFalse(hasattr(findings[0], "unsaved_vulnerability_ids")) + # Second finding has empty identifiers — same result + self.assertFalse(hasattr(findings[1], "unsaved_vulnerability_ids")) + class TestSnykParserImageLocations(DojoTestCase): @skip_unless_v3 From 780b5c637d0e7910948b791cd6da81ed1ccf95c4 Mon Sep 17 00:00:00 2001 From: Jaimin2687 Date: Mon, 21 Sep 2026 20:15:47 +0530 Subject: [PATCH 2/2] Fix test assertions and handle None score in severity_of --- dojo/tools/aqua/parser.py | 2 ++ unittests/tools/test_snyk_parser.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dojo/tools/aqua/parser.py b/dojo/tools/aqua/parser.py index fbc01c44fc6..cfe90e93928 100644 --- a/dojo/tools/aqua/parser.py +++ b/dojo/tools/aqua/parser.py @@ -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/unittests/tools/test_snyk_parser.py b/unittests/tools/test_snyk_parser.py index 4c2e48e22dd..7fa49e45bf6 100644 --- a/unittests/tools/test_snyk_parser.py +++ b/unittests/tools/test_snyk_parser.py @@ -219,10 +219,10 @@ def test_snykParser_null_identifiers(self): parser = SnykParser() findings = parser.get_findings(testfile, Test()) self.assertEqual(2, len(findings)) - # First finding has null identifiers — no CVE/CWE should be set - self.assertFalse(hasattr(findings[0], "unsaved_vulnerability_ids")) + # 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(hasattr(findings[1], "unsaved_vulnerability_ids")) + self.assertFalse(getattr(findings[1], "unsaved_vulnerability_ids", [])) class TestSnykParserImageLocations(DojoTestCase):