Skip to content

Commit d019619

Browse files
committed
improved parsing and implimented generation of unique id for deduplication
1 parent 0f6830e commit d019619

File tree

1 file changed

+100
-13
lines changed

1 file changed

+100
-13
lines changed

dojo/tools/wizcli_common_parsers/parsers.py

Lines changed: 100 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,106 @@ def extract_reference_link(text):
3232
match = re.search(r"(https?://[^\s)]+)", text)
3333
return match.group(1) if match else None
3434

35-
finding_description = (
36-
f"**Library Name**: {lib_name}\n"
37-
f"**Library Version**: {lib_version}\n"
38-
f"**Library Path**: {lib_path}\n"
39-
f"**Vulnerability Name**: {vuln_name}\n"
40-
f"**Fixed Version**: {fixed_version}\n"
41-
f"**Source**: {source}\n"
42-
f"**Description**: {description}\n"
43-
f"**Score**: {score}\n"
44-
f"**Exploitability Score**: {exploitability_score}\n"
45-
f"**Has Exploit**: {has_exploit}\n"
46-
f"**Has CISA KEV Exploit**: {has_cisa_kev_exploit}\n"
47-
)
35+
@staticmethod
36+
def _generate_unique_id(components: list) -> str:
37+
"""
38+
Generates a stable unique ID for findings.
39+
40+
Args:
41+
components: List of components to use for ID generation
42+
43+
"""
44+
# Filter out None and empty values
45+
filtered_components = [str(c).strip() for c in components if c is not None and str(c).strip()]
46+
47+
# Sort components for consistent order regardless of input order
48+
filtered_components = sorted(filtered_components)
49+
50+
id_string = "|".join(filtered_components)
51+
hash_object = hashlib.sha256(id_string.encode("utf-8"))
52+
return hash_object.hexdigest()
53+
54+
@staticmethod
55+
def parse_libraries(libraries_data, test):
56+
"""
57+
Parses library vulnerability data into granular DefectDojo findings.
58+
Creates one finding per unique vulnerability (CVE/ID) per library instance (name/version/path).
59+
"""
60+
findings_list = []
61+
if not libraries_data:
62+
return findings_list
63+
64+
for lib_item in libraries_data:
65+
lib_name = lib_item.get("name", "N/A")
66+
lib_version = lib_item.get("version", "N/A")
67+
lib_path = lib_item.get("path", "N/A")
68+
lib_line = lib_item.get("startLine")
69+
70+
vulnerabilities_in_lib_instance = lib_item.get("vulnerabilities", [])
71+
if not vulnerabilities_in_lib_instance:
72+
continue
73+
74+
for vuln_data in vulnerabilities_in_lib_instance:
75+
vuln_name = vuln_data.get("name", "N/A")
76+
severity_str = vuln_data.get("severity")
77+
severity = WizcliParsers.get_severity(severity_str)
78+
fixed_version = vuln_data.get("fixedVersion")
79+
source_url = vuln_data.get("source", "N/A")
80+
vuln_description_from_wiz = vuln_data.get("description")
81+
score_str = vuln_data.get("score")
82+
has_exploit = vuln_data.get("hasExploit", False)
83+
has_cisa_kev_exploit = vuln_data.get("hasCisaKevExploit", False)
84+
85+
title = f"{lib_name} {lib_version} - {vuln_name}"
86+
87+
description_parts = [
88+
f"**Vulnerability**: `{vuln_name}`",
89+
f"**Severity**: {severity}",
90+
f"**Library**: `{lib_name}`",
91+
f"**Version**: `{lib_version}`",
92+
f"**Path/Manifest**: `{lib_path}`",
93+
]
94+
if lib_line is not None:
95+
description_parts.append(f"**Line in Manifest**: {lib_line}")
96+
97+
if fixed_version:
98+
description_parts.append(f"**Fixed Version**: {fixed_version}")
99+
mitigation = f"Update `{lib_name}` to version `{fixed_version}` or later in path/manifest `{lib_path}`."
100+
else:
101+
description_parts.append("**Fixed Version**: N/A")
102+
mitigation = f"No fixed version available from Wiz. Investigate `{vuln_name}` for `{lib_name}` in `{lib_path}` and apply vendor guidance or risk acceptance."
103+
104+
description_parts.append(f"**Source**: {source_url}")
105+
if vuln_description_from_wiz:
106+
description_parts.append(f"\n**Details from Wiz**:\n{vuln_description_from_wiz}\n")
107+
if score_str is not None:
108+
description_parts.append(f"**CVSS Score (from Wiz)**: {score_str}")
109+
description_parts.extend([
110+
f"**Has Exploit (Known)**: {has_exploit}",
111+
f"**In CISA KEV**: {has_cisa_kev_exploit}",
112+
])
113+
114+
failed_policies = vuln_data.get("failedPolicyMatches", [])
115+
if failed_policies:
116+
description_parts.append("\n**Failed Policies**:")
117+
for match in failed_policies:
118+
policy = match.get("policy", {})
119+
description_parts.append(f"- {policy.get('name', 'N/A')} (ID: {policy.get('id', 'N/A')})")
120+
ignored_policies = vuln_data.get("ignoredPolicyMatches", [])
121+
if ignored_policies:
122+
description_parts.append("\n**Ignored Policies**:")
123+
for match in ignored_policies:
124+
policy = match.get("policy", {})
125+
reason = match.get("ignoreReason", "N/A")
126+
description_parts.append(f"- {policy.get('name', 'N/A')} (ID: {policy.get('id', 'N/A')}), Reason: {reason}")
127+
128+
full_description = "\n".join(description_parts)
129+
references = source_url if source_url != "N/A" else None
130+
131+
# Generate unique ID using stable components including file path
132+
unique_id = WizcliParsers._generate_unique_id(
133+
[lib_name, lib_version, vuln_name, lib_path],
134+
)
48135

49136
finding = Finding(
50137
title=f"{lib_name} - {vuln_name}",

0 commit comments

Comments
 (0)