Skip to content

Commit ae90b6b

Browse files
bugfix: twistlock: fix no cvss case (#12809)
* twistlock: fix no cvss case * twistlock: use markdown instead html
1 parent a0d5a03 commit ae90b6b

3 files changed

Lines changed: 147 additions & 26 deletions

File tree

dojo/tools/twistlock/parser.py

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,10 @@ def parse_issue(self, row, test):
109109
date=finding_date,
110110
severity=convert_severity(data_severity),
111111
description=data_description
112-
+ "<p> Vulnerable Package: "
112+
+ "\n\nVulnerable Package: "
113113
+ data_package_name
114-
+ "</p><p> Current Version: "
115-
+ str(data_package_version)
116-
+ "</p>",
114+
+ "\n\nCurrent Version: "
115+
+ str(data_package_version),
117116
mitigation=data_fix_status,
118117
component_name=textwrap.shorten(
119118
data_package_name, width=200, placeholder="...",
@@ -231,18 +230,10 @@ def get_item(vulnerability, test, image_metadata=""):
231230
if "severity" in vulnerability
232231
else "Info"
233232
)
234-
vector = (
235-
vulnerability.get("vector", "CVSS vector not provided. ")
236-
)
237-
status = (
238-
vulnerability.get("status", "There seems to be no fix yet. Please check description field.")
239-
)
240-
cvss = (
241-
vulnerability.get("cvss", "No CVSS score yet.")
242-
)
243-
riskFactors = (
244-
vulnerability.get("riskFactors", "No risk factors.")
245-
)
233+
cvssv3 = vulnerability.get("vector")
234+
status = vulnerability.get("status", "There seems to be no fix yet. Please check description field.")
235+
cvssv3_score = vulnerability.get("cvss")
236+
riskFactors = vulnerability.get("riskFactors", "No risk factors.")
246237

247238
# Build impact field combining severity and image metadata which can change between scans, so we add it to the impact field as the description field is sometimes used for hash code calculation
248239
impact_parts = [severity]
@@ -260,17 +251,17 @@ def get_item(vulnerability, test, image_metadata=""):
260251
test=test,
261252
severity=severity,
262253
description=vulnerability.get("description", "")
263-
+ "<p> Vulnerable Package: "
254+
+ "\n\nVulnerable Package: "
264255
+ vulnerability.get("packageName", "")
265-
+ "</p><p> Current Version: "
266-
+ str(vulnerability.get("packageVersion", ""))
267-
+ "</p>",
256+
+ "\n\nCurrent Version: "
257+
+ str(vulnerability.get("packageVersion", "")),
268258
mitigation=status.title() if isinstance(status, str) else "",
269259
references=vulnerability.get("link"),
270260
component_name=vulnerability.get("packageName", ""),
271261
component_version=vulnerability.get("packageVersion", ""),
272-
severity_justification=f"{vector} (CVSS v3 base score: {cvss})\n\n{riskFactors}",
273-
cvssv3_score=cvss,
262+
severity_justification=f"Vector: {cvssv3} (CVSS v3 base score: {cvssv3_score})\n\n{riskFactors}",
263+
cvssv3=cvssv3,
264+
cvssv3_score=cvssv3_score,
274265
impact=impact_text,
275266
)
276267
finding.unsaved_vulnerability_ids = [vulnerability["id"]] if "id" in vulnerability else None
@@ -294,15 +285,15 @@ def get_compliance_item(compliance, test, image_metadata=""):
294285
layer_time = compliance.get("layerTime", "")
295286

296287
# Build comprehensive description
297-
desc_parts = [f"<p><strong>Compliance Issue:</strong> {title}</p>"]
288+
desc_parts = [f"**Compliance Issue:** {title}\n\n"]
298289

299290
if compliance_id:
300-
desc_parts.append(f"<p><strong>Compliance ID:</strong> {compliance_id}</p>")
291+
desc_parts.append(f"**Compliance ID:** {compliance_id}\n\n")
301292

302293
if category:
303-
desc_parts.append(f"<p><strong>Category:</strong> {category}</p>")
294+
desc_parts.append(f"**Category:** {category}\n\n")
304295

305-
desc_parts.append(f"<p><strong>Description:</strong> {description}</p>")
296+
desc_parts.append(f"**Description:** {description}\n\n")
306297

307298
# Build impact field combining severity and image metadata
308299
impact_parts = [severity]
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
{
2+
"results": [
3+
{
4+
"id": "sha256:02f8d1e213aaef1e4534bd5183b17b4dc60c1be32025113b7ea20de4cb14a36e",
5+
"name": "somename:v8.11",
6+
"distro": "Debian GNU/Linux 12 (bookworm)",
7+
"distroRelease": "bookworm",
8+
"collections": [
9+
"All collections",
10+
"Group (RBAC)"
11+
],
12+
"packages": [
13+
{
14+
"type": "os",
15+
"name": "bash",
16+
"version": "5.2.15-2",
17+
"licenses": [
18+
"GPL-3+"
19+
]
20+
},
21+
{
22+
"type": "python",
23+
"name": "inflect",
24+
"version": "7.3.1",
25+
"path": "/usr/local/lib/python3.13/site-packages/setuptools/_vendor/inflect-7.3.1.dist-info"
26+
}
27+
],
28+
"compliances": [
29+
{
30+
"id": 404,
31+
"title": "(CIS_Docker_v1.5.0 - 4.6) Add HEALTHCHECK instruction to the container image",
32+
"severity": "medium",
33+
"description": "One of the important security triads is availability. Adding HEALTHCHECK instruction to your\ncontainer image ensures that the docker engine periodically checks the running container\ninstances against that instruction to ensure that the instances are still working",
34+
"layerTime": "1970-01-01T00:00:00Z",
35+
"category": "Docker"
36+
}
37+
],
38+
"complianceDistribution": {
39+
"critical": 0,
40+
"high": 0,
41+
"medium": 1,
42+
"low": 0,
43+
"total": 1
44+
},
45+
"complianceScanPassed": true,
46+
"vulnerabilities": [
47+
{
48+
"id": "CVE-2025-6297",
49+
"status": "open",
50+
"severity": "low",
51+
"packageName": "dpkg",
52+
"packageVersion": "1.21.22",
53+
"link": "https://security-tracker.debian.org/tracker/CVE-2025-6297",
54+
"impactedVersions": [
55+
"\u003c=1.21.22"
56+
],
57+
"discoveredDate": "2025-07-08T09:50:42Z",
58+
"layerTime": "2025-02-26T09:50:35Z",
59+
"layerInstruction": "COPY extra/instantclient-basic-linux.x64-19.16.0.0.0dbru.zip /tmp/instantclient-basic-linux.x64-19.16.0.0.0dbru.zip # buildkit"
60+
},
61+
{
62+
"id": "CVE-2025-1390",
63+
"status": "fixed in 1:2.66-4+deb12u1",
64+
"description": "The PAM module pam_cap.so of libcap configuration supports group names starting with “@”, during actual parsing, configurations not starting with “@” are incorrectly recognized as group names. This may result in nonintended users being granted an inherited capability set, potentially leading to security risks. Attackers can exploit this vulnerability to achieve local privilege escalation on systems where /etc/security/capability.conf is used to configure user inherited privileges by constructing specific usernames.",
65+
"severity": "low",
66+
"packageName": "libcap2",
67+
"packageVersion": "1:2.66-4",
68+
"link": "https://security-tracker.debian.org/tracker/CVE-2025-1390",
69+
"riskFactors": [
70+
"Has fix",
71+
"Recent vulnerability"
72+
],
73+
"impactedVersions": [
74+
"\u003c1:2.66-4+deb12u1"
75+
],
76+
"publishedDate": "2025-02-18T03:15:10Z",
77+
"discoveredDate": "2025-07-08T09:50:42Z",
78+
"fixDate": "2025-05-17T14:08:50Z",
79+
"layerTime": "2025-02-26T09:50:35Z",
80+
"layerInstruction": "COPY extra/instantclient-basic-linux.x64-19.16.0.0.0dbru.zip /tmp/instantclient-basic-linux.x64-19.16.0.0.0dbru.zip # buildkit"
81+
}
82+
],
83+
"vulnerabilityDistribution": {
84+
"critical": 0,
85+
"high": 1,
86+
"medium": 0,
87+
"low": 2,
88+
"total": 3
89+
},
90+
"vulnerabilityScanPassed": true,
91+
"history": [
92+
{
93+
"created": "2025-01-01T21:11:11Z",
94+
"instruction": "bashstring -i -F"
95+
}
96+
],
97+
"scanTime": "2025-01-01T02:12:23.309542313Z",
98+
"scanID": "686cea70f70e81f2b73eb5c3"
99+
}
100+
],
101+
"consoleURL": "https://canadaconsoleforthearts.gov.ca"
102+
}

unittests/tools/test_twistlock_parser.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11

2+
23
from dojo.models import Test
34
from dojo.tools.twistlock.parser import TwistlockParser
45
from unittests.dojo_test_case import DojoTestCase, get_unit_tests_scans_path
@@ -114,6 +115,33 @@ def test_parse_file_with_no_link_no_description(self):
114115
self.assertEqual("PRISMA-2021-0013", finding.unsaved_vulnerability_ids[0])
115116
break
116117

118+
def test_parse_file_with_no_cvss(self):
119+
testfile = (get_unit_tests_scans_path("twistlock") / "no_cvss.json").open(encoding="utf-8")
120+
parser = TwistlockParser()
121+
findings = parser.get_findings(testfile, Test())
122+
testfile.close()
123+
# Should have 4 findings: 3 vulnerabilities + 1 compliance
124+
self.assertEqual(3, len(findings))
125+
126+
for finding in findings:
127+
if finding.title.startswith("Compliance:"):
128+
# Verify compliance finding exists
129+
self.assertIn("CIS_Docker_v1.5.0 - 4.6", finding.title)
130+
self.assertEqual("Medium", finding.severity)
131+
self.assertIn("404", finding.vuln_id_from_tool)
132+
else:
133+
# This should be a vulnerability finding
134+
self.assertEqual(1, len(finding.unsaved_vulnerability_ids))
135+
finding.unsaved_vulnerability_ids[0]
136+
# Findings without CVSS should have None or empty CVSS fields
137+
self.assertIsNone(finding.cvssv3)
138+
self.assertIsNone(finding.cvssv3_score)
139+
140+
# All vulnerability findings should have impact metadata
141+
self.assertIn("Image ID:", finding.impact)
142+
self.assertIn("Distribution:", finding.impact)
143+
self.assertIn("Debian GNU/Linux 12", finding.impact)
144+
117145
def test_parse_file_with_many_vulns(self):
118146
testfile = (get_unit_tests_scans_path("twistlock") / "many_vulns.json").open(encoding="utf-8")
119147
parser = TwistlockParser()

0 commit comments

Comments
 (0)