ds-reporting-lib 6.3.0__tar.gz → 6.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ds-reporting-lib
3
- Version: 6.3.0
3
+ Version: 6.3.2
4
4
  Summary: Reporting library for owasp depscan
5
5
  Author-email: Team AppThreat <cloud@appthreat.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ds-reporting-lib"
3
- version = "6.3.0"
3
+ version = "6.3.2"
4
4
  description = "Reporting library for owasp depscan"
5
5
  authors = [
6
6
  {name = "Team AppThreat", email = "cloud@appthreat.com"},
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ds-reporting-lib
3
- Version: 6.3.0
3
+ Version: 6.3.2
4
4
  Summary: Reporting library for owasp depscan
5
5
  Author-email: Team AppThreat <cloud@appthreat.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import html
3
3
  import re
4
+ from typing import Any, Dict, List, Optional
4
5
 
5
6
  from reporting_lib.template import (
6
7
  PRIORITIZED_VULNERABILITIES,
@@ -123,11 +124,11 @@ class ReportGenerator:
123
124
 
124
125
  def __init__(
125
126
  self,
126
- report_output_path,
127
- input_vdr_json_path=None,
128
- input_rich_html_path=None,
129
- input_txt_path=None,
130
- raw_content=False,
127
+ report_output_path: str,
128
+ input_vdr_json_path: Optional[str] = None,
129
+ input_rich_html_path: Optional[str] = None,
130
+ input_txt_path: Optional[str] = None,
131
+ raw_content: bool = False,
131
132
  ):
132
133
  input_counter = 0
133
134
 
@@ -150,8 +151,11 @@ class ReportGenerator:
150
151
  self.input_txt_path = input_txt_path
151
152
  self.raw_content = raw_content
152
153
 
153
- def extract_depscan_reports_from_vdr_json(self):
154
+ def extract_depscan_reports_from_vdr_json(self) -> List[str]:
154
155
  depscan_reports = set()
156
+ # The constructor guarantees exactly one input path was supplied; the
157
+ # caller only reaches here for the VDR-JSON flavour.
158
+ assert self.input_vdr_json_path is not None
155
159
  with open(self.input_vdr_json_path, "r", encoding="utf-8") as file:
156
160
  data = json.load(file)
157
161
  if "annotations" not in data:
@@ -172,6 +176,7 @@ class ReportGenerator:
172
176
  return list(depscan_reports)
173
177
 
174
178
  def extract_depscan_report_from_rich_html(self):
179
+ assert self.input_rich_html_path is not None
175
180
  with open(self.input_rich_html_path, "r", encoding="utf-8") as file:
176
181
  data = file.read()
177
182
 
@@ -193,6 +198,7 @@ class ReportGenerator:
193
198
  return depscan_report, styles
194
199
 
195
200
  def extract_depscan_report_from_txt(self):
201
+ assert self.input_txt_path is not None
196
202
  with open(self.input_txt_path, "r", encoding="utf-8") as file:
197
203
  data = file.read()
198
204
  return data
@@ -284,13 +290,18 @@ class ReportGenerator:
284
290
  last_seen_reachable_flows = None
285
291
  current_location = None
286
292
  current_table_row = []
287
- current_columns_count = None
293
+ # Set from the table's top border when a data section starts; the
294
+ # rsplit(maxsplit=...) sites below only run inside that state.
295
+ current_columns_count = 0
288
296
  summary_column = ""
289
297
  reachable_flow_column = ""
290
298
  reachable_packages_column = ""
291
299
  recommendation_column = ""
292
300
 
293
- sections_tree = {
301
+ # Parsed report sections keyed by display name. The tree mixes leaf
302
+ # strings and nested dicts (per-section summary/headers/data), so the
303
+ # values are deliberately typed as Any.
304
+ sections_tree: Dict[str, Any] = {
294
305
  self.VULNERABILITY_DISCLOSURE_REPORT: {
295
306
  self.DEPENDENCY_SCAN_RESULTS_BOM: {
296
307
  self.TITLE: self.DEPENDENCY_SCAN_RESULTS_BOM,
@@ -569,7 +580,9 @@ class ReportGenerator:
569
580
 
570
581
  if (
571
582
  current_location == self.DEPENDENCY_SCAN_RESULTS_BOM
572
- and self.normalized_text_matches(line, r"Vulnerabilities count: \d+")
583
+ and self.normalized_text_matches(
584
+ line, r"Vulnerabilities count: \d+( \(\d+ unique\))?"
585
+ )
573
586
  ):
574
587
  sections_tree[self.VULNERABILITY_DISCLOSURE_REPORT][
575
588
  self.DEPENDENCY_SCAN_RESULTS_BOM
@@ -1418,11 +1431,15 @@ class ReportGenerator:
1418
1431
  if tree_is_from_html is False:
1419
1432
  current_content = html.escape(current_content)
1420
1433
 
1421
- try:
1422
- vulnerabilities_count = int(current_content)
1423
- except ValueError:
1434
+ # The caption counts (id, affects) pairs and may append the unique
1435
+ # vulnerability count when merged entries were expanded per component
1436
+ # (discussion #527 follow-up). Only the leading number gates the
1437
+ # recount fallback; the suffix stays in the badge.
1438
+ count_match = re.fullmatch(r"(\d+)( \(\d+ unique\))?", current_content)
1439
+ if count_match:
1440
+ vulnerabilities_count = int(count_match.group(1))
1441
+ else:
1424
1442
  vulnerabilities_count = -1
1425
- pass
1426
1443
  if vulnerabilities_count == -1:
1427
1444
  all_cves = set()
1428
1445
  for row_data in sections_tree[self.VULNERABILITY_DISCLOSURE_REPORT][
@@ -2197,13 +2214,12 @@ class ReportGenerator:
2197
2214
  self, sections_tree, report_content, table_inits, tree_is_from_html, piece_id
2198
2215
  ):
2199
2216
  info = INFO
2200
- malware_alert = MALWARE_ALERT
2201
2217
  if piece_id is None:
2202
2218
  info = info.replace("<PIECE_ID_PLACEHOLDER>", "", 1)
2203
2219
  else:
2204
2220
  if tree_is_from_html is False:
2205
2221
  piece_id = html.escape(piece_id)
2206
- info = malware_alert.info("<PIECE_ID_PLACEHOLDER>", f"{piece_id} {SEPARATOR} ", 1)
2222
+ info = info.replace("<PIECE_ID_PLACEHOLDER>", f"{piece_id} {SEPARATOR} ", 1)
2207
2223
 
2208
2224
  current_content = sections_tree[self.INFO][self.SUMMARY]
2209
2225
  if tree_is_from_html is False:
@@ -2222,13 +2238,15 @@ class ReportGenerator:
2222
2238
 
2223
2239
  def generate_html(
2224
2240
  self,
2225
- sections_tree=None,
2226
- sections_trees=None,
2227
- tree_is_from_html=False,
2228
- styles="",
2241
+ sections_tree: Optional[Dict[str, Any]] = None,
2242
+ sections_trees: Optional[Dict[Any, Dict[str, Any]]] = None,
2243
+ tree_is_from_html: bool = False,
2244
+ styles: str = "",
2229
2245
  ):
2230
2246
  if sections_tree is not None:
2231
2247
  sections_trees = {None: sections_tree}
2248
+ if sections_trees is None:
2249
+ raise ValueError("generate_html requires sections_tree or sections_trees")
2232
2250
 
2233
2251
  main_report = HTML_REPORT
2234
2252