Skip to content

Commit e702f12

Browse files
stevewalloneclaude
andauthored
fix(checkmarx_one): handle explicit null scanner sections in filtered reports (#15159)
Reports filtered to a subset of scanners (e.g. SAST-only) contain the other sections as explicit nulls ("iacScanResults": null), so dict.get(key, {}) returns None and the chained .get() raises AttributeError: 'NoneType' object has no attribute 'get'. A null vulnerabilityDetails section likewise crashed CWE lookups with TypeError: 'NoneType' object is not iterable. Fall back to empty containers with (data.get(key) or {}) for scanResults, iacScanResults and scaScanResults, and or [] for vulnerabilityDetails. Adds two regression fixtures covering each crash path: a SAST-only report with null scanner sections and a populated CWE store, and a variant with vulnerabilityDetails also null. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 94a049b commit e702f12

4 files changed

Lines changed: 245 additions & 4 deletions

File tree

dojo/tools/checkmarx_one/parser.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,17 @@ def parse_vulnerabilities_from_scan_list(
4141
data: dict,
4242
) -> list[Finding]:
4343
findings = []
44-
cwe_store = data.get("vulnerabilityDetails", [])
44+
# Reports filtered to a subset of scanners may contain the other
45+
# sections as explicit nulls, so fall back to empty containers
46+
cwe_store = data.get("vulnerabilityDetails") or []
4547
# SAST
46-
if (results := data.get("scanResults", {}).get("resultsList")) is not None:
48+
if (results := (data.get("scanResults") or {}).get("resultsList")) is not None:
4749
findings += self.parse_sast_vulnerabilities(test, results, cwe_store)
4850
# IaC
49-
if (results := data.get("iacScanResults", {}).get("technology")) is not None:
51+
if (results := (data.get("iacScanResults") or {}).get("technology")) is not None:
5052
findings += self.parse_iac_vulnerabilities(test, results, cwe_store)
5153
# SCA
52-
if (results := data.get("scaScanResults", {}).get("packages")) is not None:
54+
if (results := (data.get("scaScanResults") or {}).get("packages")) is not None:
5355
findings += self.parse_sca_vulnerabilities(test, results, cwe_store)
5456
return findings
5557

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
{
2+
"reportCreationDate": "2026-06-30T10:15:00.000000Z",
3+
"reportType": "scan",
4+
"reportHeader": {
5+
"reportName": "improved-scan-report",
6+
"projectName": "sast-only-project",
7+
"branchName": "main"
8+
},
9+
"scanResults": {
10+
"totalResults": 2,
11+
"resultsList": [
12+
{
13+
"queryName": "Reflected_XSS",
14+
"queryId": 9349880549689768000,
15+
"description": "The method embeds untrusted data in generated output, enabling an attacker to inject malicious code into the generated web-page.",
16+
"vulnerabilitiesTotal": 1,
17+
"queryPath": "Kotlin/Kotlin_High_Risk/Reflected_XSS",
18+
"cweId": 79,
19+
"categories": [
20+
{
21+
"name": "OWASP Top 10 2021",
22+
"subCategories": [
23+
"A3-Injection"
24+
]
25+
}
26+
],
27+
"vulnerabilities": [
28+
{
29+
"similarityId": 134962663,
30+
"status": "Recurrent",
31+
"state": "To Verify",
32+
"severity": "High",
33+
"foundDate": "2026-06-16T14:39:59Z",
34+
"firstFoundDate": "2026-03-18T20:51:25Z",
35+
"sourceFileName": "/src/main/kotlin/api/CustomerController.kt",
36+
"destinationFileName": "/src/main/kotlin/domain/entity/Customer.kt",
37+
"sourceLine": 69,
38+
"destinationLine": 484,
39+
"nodes": [
40+
{
41+
"fileName": "/src/main/kotlin/api/CustomerController.kt",
42+
"method": "putCustomer",
43+
"line": 69,
44+
"code": "@RequestBody s: S"
45+
}
46+
]
47+
}
48+
]
49+
},
50+
{
51+
"queryName": "Deprecated_API",
52+
"queryId": 9349880549689768001,
53+
"description": "The application references code elements that have been declared as deprecated.",
54+
"vulnerabilitiesTotal": 1,
55+
"queryPath": "Go/Go_Low_Visibility/Deprecated_API",
56+
"cweId": 477,
57+
"categories": [
58+
{
59+
"name": "OWASP ASVS",
60+
"subCategories": [
61+
"V01 Architecture, Design and Threat Modeling"
62+
]
63+
}
64+
],
65+
"vulnerabilities": [
66+
{
67+
"similarityId": 872206556,
68+
"status": "New",
69+
"state": "To Verify",
70+
"severity": "Low",
71+
"foundDate": "2026-06-16T14:39:59Z",
72+
"firstFoundDate": "2026-06-16T14:39:59Z",
73+
"sourceFileName": "/scripts/export/e.go",
74+
"destinationFileName": "/scripts/export/e.go",
75+
"sourceLine": 9,
76+
"destinationLine": 9,
77+
"nodes": [
78+
{
79+
"fileName": "/scripts/export/e.go",
80+
"method": "main",
81+
"line": 9,
82+
"code": "ioutil.ReadFile(path)"
83+
}
84+
]
85+
}
86+
]
87+
}
88+
]
89+
},
90+
"iacScanResults": null,
91+
"scaScanResults": null,
92+
"containerScanResults": null,
93+
"apiSecScanResults": null,
94+
"vulnerabilityDetails": null
95+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
{
2+
"reportCreationDate": "2026-06-30T10:15:00.000000Z",
3+
"reportType": "scan",
4+
"reportHeader": {
5+
"reportName": "improved-scan-report",
6+
"projectName": "sast-only-project",
7+
"branchName": "main"
8+
},
9+
"scanResults": {
10+
"totalResults": 2,
11+
"resultsList": [
12+
{
13+
"queryName": "Reflected_XSS",
14+
"queryId": 9349880549689768000,
15+
"description": "The method embeds untrusted data in generated output, enabling an attacker to inject malicious code into the generated web-page.",
16+
"vulnerabilitiesTotal": 1,
17+
"queryPath": "Kotlin/Kotlin_High_Risk/Reflected_XSS",
18+
"cweId": 79,
19+
"categories": [
20+
{
21+
"name": "OWASP Top 10 2021",
22+
"subCategories": [
23+
"A3-Injection"
24+
]
25+
}
26+
],
27+
"vulnerabilities": [
28+
{
29+
"similarityId": 134962663,
30+
"status": "Recurrent",
31+
"state": "To Verify",
32+
"severity": "High",
33+
"foundDate": "2026-06-16T14:39:59Z",
34+
"firstFoundDate": "2026-03-18T20:51:25Z",
35+
"sourceFileName": "/src/main/kotlin/api/CustomerController.kt",
36+
"destinationFileName": "/src/main/kotlin/domain/entity/Customer.kt",
37+
"sourceLine": 69,
38+
"destinationLine": 484,
39+
"nodes": [
40+
{
41+
"fileName": "/src/main/kotlin/api/CustomerController.kt",
42+
"method": "putCustomer",
43+
"line": 69,
44+
"code": "@RequestBody s: S"
45+
}
46+
]
47+
}
48+
]
49+
},
50+
{
51+
"queryName": "Deprecated_API",
52+
"queryId": 9349880549689768001,
53+
"description": "The application references code elements that have been declared as deprecated.",
54+
"vulnerabilitiesTotal": 1,
55+
"queryPath": "Go/Go_Low_Visibility/Deprecated_API",
56+
"cweId": 477,
57+
"categories": [
58+
{
59+
"name": "OWASP ASVS",
60+
"subCategories": [
61+
"V01 Architecture, Design and Threat Modeling"
62+
]
63+
}
64+
],
65+
"vulnerabilities": [
66+
{
67+
"similarityId": 872206556,
68+
"status": "New",
69+
"state": "To Verify",
70+
"severity": "Low",
71+
"foundDate": "2026-06-16T14:39:59Z",
72+
"firstFoundDate": "2026-06-16T14:39:59Z",
73+
"sourceFileName": "/scripts/export/e.go",
74+
"destinationFileName": "/scripts/export/e.go",
75+
"sourceLine": 9,
76+
"destinationLine": 9,
77+
"nodes": [
78+
{
79+
"fileName": "/scripts/export/e.go",
80+
"method": "main",
81+
"line": 9,
82+
"code": "ioutil.ReadFile(path)"
83+
}
84+
]
85+
}
86+
]
87+
}
88+
]
89+
},
90+
"iacScanResults": null,
91+
"scaScanResults": null,
92+
"containerScanResults": null,
93+
"apiSecScanResults": null,
94+
"vulnerabilityDetails": [
95+
{
96+
"vulnerabilityName": "Reflected_XSS",
97+
"risk": "A successful XSS exploit would allow an attacker to rewrite web pages and insert malicious scripts.",
98+
"cause": "The application creates web pages that include untrusted data without proper encoding.",
99+
"generalRecommendations": "Fully encode all dynamic data before embedding it in output.",
100+
"cweId": 79
101+
},
102+
{
103+
"vulnerabilityName": "Deprecated_API",
104+
"risk": "Referencing deprecated modules can expose the application to known vulnerabilities.",
105+
"cause": "The application references code elements that have been declared as deprecated.",
106+
"generalRecommendations": "Always prefer to use the most updated versions of libraries and packages.",
107+
"cweId": 477
108+
}
109+
]
110+
}

unittests/tools/test_checkmarx_one_parser.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,40 @@ def test_sca_finding(finding):
154154
self.maxDiff = None
155155
test_sast_finding(sast_finding)
156156

157+
def test_checkmarx_vulnerabilities_from_scan_results_sast_only(self):
158+
"""Reports filtered to SAST contain the other scanner sections as explicit nulls."""
159+
with (get_unit_tests_scans_path("checkmarx_one") / "vulnerabilities_from_scan_results_sast_only.json").open(encoding="utf-8") as testfile:
160+
parser = CheckmarxOneParser()
161+
findings = parser.get_findings(testfile, Test())
162+
self.assertEqual(2, len(findings))
163+
with self.subTest(i=0):
164+
for finding in findings:
165+
self.assertIsNotNone(finding.title)
166+
self.assertIsNotNone(finding.test)
167+
self.assertIsNotNone(finding.date)
168+
self.assertIsNotNone(finding.severity)
169+
self.assertIsNotNone(finding.description)
170+
self.assertEqual(["sast"], finding.unsaved_tags)
171+
finding_test = findings[0]
172+
self.assertEqual("High", finding_test.severity)
173+
self.assertEqual("Kotlin/Kotlin High Risk/Reflected XSS", finding_test.title)
174+
self.assertEqual("/src/main/kotlin/domain/entity/Customer.kt", finding_test.file_path)
175+
# CWE details come from the populated vulnerabilityDetails store
176+
self.assertEqual(79, finding_test.cwe)
177+
self.assertEqual("Fully encode all dynamic data before embedding it in output.", finding_test.mitigation)
178+
179+
def test_checkmarx_vulnerabilities_from_scan_results_null_vulnerability_details(self):
180+
"""A null vulnerabilityDetails section must not break parsing of SAST results."""
181+
with (get_unit_tests_scans_path("checkmarx_one") / "vulnerabilities_from_scan_results_null_details.json").open(encoding="utf-8") as testfile:
182+
parser = CheckmarxOneParser()
183+
findings = parser.get_findings(testfile, Test())
184+
self.assertEqual(2, len(findings))
185+
finding_test = findings[0]
186+
self.assertEqual("High", finding_test.severity)
187+
self.assertEqual("Kotlin/Kotlin High Risk/Reflected XSS", finding_test.title)
188+
# No CWE store available, so store-derived fields fall back to empty
189+
self.assertEqual("", finding_test.mitigation)
190+
157191
def test_checkmarx_one_false_positive_status(self):
158192
with (get_unit_tests_scans_path("checkmarx_one") / "one-open-one-false-positive.json").open(encoding="utf-8") as testfile:
159193
parser = CheckmarxOneParser()

0 commit comments

Comments
 (0)