@@ -97,6 +97,7 @@ def output_html(
9797 outfile ,
9898 affected_versions : int = 0 ,
9999 strip_scan_dir : bool = False ,
100+ no_scan : bool = False ,
100101):
101102 """Returns a HTML report for CVE's"""
102103
@@ -156,15 +157,27 @@ def output_html(
156157 # Start generating graph with the data
157158
158159 # dash graph1: Products Vulnerability Graph
159- product_pie = go .Figure (
160- data = [
161- go .Pie (
162- labels = ["Vulnerable" , "No Known Vulnerability" ],
163- values = [products_with_cve , products_without_cve ],
164- hole = 0.4 ,
165- )
166- ]
167- )
160+ if no_scan :
161+ # In no-scan mode, show detected products vs no products
162+ product_pie = go .Figure (
163+ data = [
164+ go .Pie (
165+ labels = ["Detected Products" , "No Products Detected" ],
166+ values = [products_with_cve + products_without_cve , 0 ],
167+ hole = 0.4 ,
168+ )
169+ ]
170+ )
171+ else :
172+ product_pie = go .Figure (
173+ data = [
174+ go .Pie (
175+ labels = ["Vulnerable" , "No Known Vulnerability" ],
176+ values = [products_with_cve , products_without_cve ],
177+ hole = 0.4 ,
178+ )
179+ ]
180+ )
168181
169182 # Chart configuration for product_pie
170183 product_pie .update_layout (
@@ -183,46 +196,50 @@ def output_html(
183196
184197 # dash graph2: Product CVE's Graph
185198 cve_bar = go .Figure ()
186- data_by_cve_remarks : dict = {
187- "NEW" : {"x" : [], "y" : []},
188- "MITIGATED" : {"x" : [], "y" : []},
189- "CONFIRMED" : {"x" : [], "y" : []},
190- "UNEXPLORED" : {"x" : [], "y" : []},
191- "FALSE POSITIVE" : {"x" : [], "y" : []},
192- "NOT AFFECTED" : {"x" : [], "y" : []},
193- }
194- for product_info , cve_data in all_cve_data .items ():
195- # Check if product contains CVEs
196- if cve_data ["cves" ]:
197- cve_by_remark = group_cve_by_remark (cve_data ["cves" ])
198- for key , val in [
199- ["NEW" , len (cve_by_remark [Remarks .NewFound ])],
200- ["MITIGATED" , len (cve_by_remark [Remarks .Mitigated ])],
201- ["CONFIRMED" , len (cve_by_remark [Remarks .Confirmed ])],
202- ["UNEXPLORED" , len (cve_by_remark [Remarks .Unexplored ])],
203- ["FALSE POSITIVE" , len (cve_by_remark [Remarks .FalsePositive ])],
204- ["NOT AFFECTED" , len (cve_by_remark [Remarks .NotAffected ])],
205- ]:
206- x = (
207- f"{ product_info .vendor } -{ product_info .product } ({ product_info .version } )"
208- if product_info .vendor != "UNKNOWN"
209- else f"{ product_info .product } ({ product_info .version } )"
199+ if not no_scan :
200+ data_by_cve_remarks : dict = {
201+ "NEW" : {"x" : [], "y" : []},
202+ "MITIGATED" : {"x" : [], "y" : []},
203+ "CONFIRMED" : {"x" : [], "y" : []},
204+ "UNEXPLORED" : {"x" : [], "y" : []},
205+ "FALSE POSITIVE" : {"x" : [], "y" : []},
206+ "NOT AFFECTED" : {"x" : [], "y" : []},
207+ }
208+ for product_info , cve_data in all_cve_data .items ():
209+ # Check if product contains CVEs
210+ if cve_data ["cves" ]:
211+ cve_by_remark = group_cve_by_remark (cve_data ["cves" ])
212+ for key , val in [
213+ ["NEW" , len (cve_by_remark [Remarks .NewFound ])],
214+ ["MITIGATED" , len (cve_by_remark [Remarks .Mitigated ])],
215+ ["CONFIRMED" , len (cve_by_remark [Remarks .Confirmed ])],
216+ ["UNEXPLORED" , len (cve_by_remark [Remarks .Unexplored ])],
217+ ["FALSE POSITIVE" , len (cve_by_remark [Remarks .FalsePositive ])],
218+ ["NOT AFFECTED" , len (cve_by_remark [Remarks .NotAffected ])],
219+ ]:
220+ x = (
221+ f"{ product_info .vendor } -{ product_info .product } ({ product_info .version } )"
222+ if product_info .vendor != "UNKNOWN"
223+ else f"{ product_info .product } ({ product_info .version } )"
224+ )
225+ y = 0 if cve_data ["cves" ][0 ][1 ] == "UNKNOWN" else val
226+ data_by_cve_remarks [key ]["x" ].append (x )
227+ data_by_cve_remarks [key ]["y" ].append (y )
228+
229+ for key , val in data_by_cve_remarks .items ():
230+ cve_bar .add_trace (
231+ go .Bar (
232+ x = val ["x" ],
233+ y = val ["y" ],
234+ name = key ,
210235 )
211- y = 0 if cve_data ["cves" ][0 ][1 ] == "UNKNOWN" else val
212- data_by_cve_remarks [key ]["x" ].append (x )
213- data_by_cve_remarks [key ]["y" ].append (y )
214-
215- for key , val in data_by_cve_remarks .items ():
216- cve_bar .add_trace (
217- go .Bar (
218- x = val ["x" ],
219- y = val ["y" ],
220- name = key ,
221236 )
222- )
223237
224238 # Chart configuration for cve_bar
225- cve_bar .update_layout (yaxis_title = "Number of CVE's" , barmode = "stack" )
239+ if no_scan :
240+ cve_bar .update_layout (yaxis_title = "No CVE Analysis Performed" , barmode = "stack" )
241+ else :
242+ cve_bar .update_layout (yaxis_title = "Number of CVE's" , barmode = "stack" )
226243
227244 all_paths = defaultdict (list )
228245
@@ -240,56 +257,71 @@ def output_html(
240257 cve_severity = {"CRITICAL" : 0 , "HIGH" : 0 , "MEDIUM" : 0 , "LOW" : 0 , "UNKNOWN" : 0 }
241258
242259 cve_by_metrics : defaultdict [Remarks , list [dict [str , str ]]] = defaultdict (list )
243- for product_info , cve_data in all_cve_data .items ():
244- if cve_data ["cves" ]:
245- for cve in cve_data ["cves" ]:
246- probability = "-"
247- percentile = "-"
248-
249- for metric , field in cve .metric .items ():
250- if metric == "EPSS" :
251- probability = round (field [0 ] * 100 , 4 )
252- percentile = field [1 ]
253-
254- cve_by_metrics [cve .remarks ].append (
255- {
256- "cve_number" : cve .cve_number ,
257- "cvss_version" : str (cve .cvss_version ),
258- "cvss_score" : str (cve .score ),
259- "epss_probability" : str (probability ),
260- "epss_percentile" : str (percentile ),
261- "severity" : cve .severity ,
262- }
263- )
260+ if not no_scan :
261+ for product_info , cve_data in all_cve_data .items ():
262+ if cve_data ["cves" ]:
263+ for cve in cve_data ["cves" ]:
264+ probability = "-"
265+ percentile = "-"
266+
267+ for metric , field in cve .metric .items ():
268+ if metric == "EPSS" :
269+ probability = round (field [0 ] * 100 , 4 )
270+ percentile = field [1 ]
271+
272+ cve_by_metrics [cve .remarks ].append (
273+ {
274+ "cve_number" : cve .cve_number ,
275+ "cvss_version" : str (cve .cvss_version ),
276+ "cvss_score" : str (cve .score ),
277+ "epss_probability" : str (probability ),
278+ "epss_percentile" : str (percentile ),
279+ "severity" : cve .severity ,
280+ }
281+ )
264282
265283 cve_metric_html_rows = []
266- for remarks in sorted (cve_by_metrics ):
267- for cve in cve_by_metrics [remarks ]:
268- row_color = "table-success"
269- if cve ["severity" ] == "CRITICAL" :
270- row_color = "table-danger"
271- elif cve ["severity" ] == "HIGH" :
272- row_color = "table-primary"
273- elif cve ["severity" ] == "MEDIUM" :
274- row_color = "table-warning"
275-
276- html_row = f"""
277- <tr class="{ row_color } ">
278- <th scope="row">{ cve ["cve_number" ]} </th>
279- <td>{ cve ["cvss_version" ]} </td>
280- <td>{ cve ["cvss_score" ]} </td>
281- <td>{ cve ["epss_probability" ]} </td>
282- <td>{ cve ["epss_percentile" ]} </td>
283- </tr>
284- """
285- cve_metric_html_rows .append (html_row )
284+ if not no_scan :
285+ for remarks in sorted (cve_by_metrics ):
286+ for cve in cve_by_metrics [remarks ]:
287+ row_color = "table-success"
288+ if cve ["severity" ] == "CRITICAL" :
289+ row_color = "table-danger"
290+ elif cve ["severity" ] == "HIGH" :
291+ row_color = "table-primary"
292+ elif cve ["severity" ] == "MEDIUM" :
293+ row_color = "table-warning"
294+
295+ html_row = f"""
296+ <tr class="{ row_color } ">
297+ <th scope="row">{ cve ["cve_number" ]} </th>
298+ <td>{ cve ["cvss_version" ]} </td>
299+ <td>{ cve ["cvss_score" ]} </td>
300+ <td>{ cve ["epss_probability" ]} </td>
301+ <td>{ cve ["epss_percentile" ]} </td>
302+ </tr>
303+ """
304+ cve_metric_html_rows .append (html_row )
286305 # Join the HTML rows to create the full table content
287306 table_content = "\n " .join (cve_metric_html_rows )
288307
289308 # List of Products
290309 for product_info , cve_data in all_cve_data .items ():
291- # Check if product contains CVEs
292- if cve_data ["cves" ]:
310+ # hid is unique for each product
311+ if product_info .vendor != "UNKNOWN" :
312+ hid = f"{ product_info .vendor } { product_info .product } { '' .join (product_info .version .split ('.' ))} "
313+ else :
314+ hid = f"{ product_info .product } { '' .join (product_info .version .split ('.' ))} "
315+
316+ if strip_scan_dir :
317+ product_paths = [
318+ strip_path (path , scanned_dir ) for path in cve_data ["paths" ]
319+ ]
320+ else :
321+ product_paths = cve_data ["paths" ]
322+
323+ if not no_scan and cve_data ["cves" ]:
324+ # Process products with CVEs in normal scan mode
293325 # group product wise cves on the basis of remarks
294326 cve_by_remark = group_cve_by_remark (cve_data ["cves" ])
295327
@@ -304,13 +336,6 @@ def output_html(
304336 norm_severity = normalize_severity (cve .severity )
305337 cve_severity [norm_severity ] += 1
306338
307- # hid is unique for each product
308- if product_info .vendor != "UNKNOWN" :
309- hid = f"{ product_info .vendor } { product_info .product } { '' .join (product_info .version .split ('.' ))} "
310- else :
311- hid = (
312- f"{ product_info .product } { '' .join (product_info .version .split ('.' ))} "
313- )
314339 new_cves = render_cves (
315340 hid ,
316341 cve_row ,
@@ -404,13 +429,6 @@ def output_html(
404429 if not_affected_cves :
405430 remarks += "not_affected "
406431
407- if strip_scan_dir :
408- product_paths = [
409- strip_path (path , scanned_dir ) for path in cve_data ["paths" ]
410- ]
411- else :
412- product_paths = cve_data ["paths" ]
413-
414432 products_found .append (
415433 product_row .render (
416434 vendor = product_info .vendor ,
@@ -432,13 +450,56 @@ def output_html(
432450 not_affected_cves = not_affected_cves ,
433451 )
434452 )
453+ else :
454+ # Process products in no-scan mode or products without CVEs
455+ if no_scan :
456+ remarks = "no_scan"
457+ cve_count = 0
458+ severity_analysis = ""
459+ new_cves = ""
460+ mitigated_cves = ""
461+ confirmed_cves = ""
462+ unexplored_cves = ""
463+ false_positive_cves = ""
464+ not_affected_cves = ""
465+ else :
466+ # Products without CVEs in normal scan mode
467+ remarks = "no_cves"
468+ cve_count = 0
469+ severity_analysis = ""
470+ new_cves = ""
471+ mitigated_cves = ""
472+ confirmed_cves = ""
473+ unexplored_cves = ""
474+ false_positive_cves = ""
475+ not_affected_cves = ""
476+
477+ products_found .append (
478+ product_row .render (
479+ vendor = product_info .vendor ,
480+ name = product_info .product ,
481+ version = product_info .version ,
482+ cve_count = cve_count ,
483+ severity_analysis = severity_analysis ,
484+ remarks = remarks ,
485+ fix_id = hid ,
486+ paths = product_paths ,
487+ len_paths = len (product_paths ),
488+ new_cves = new_cves ,
489+ mitigated_cves = mitigated_cves ,
490+ confirmed_cves = confirmed_cves ,
491+ unexplored_cves = unexplored_cves ,
492+ false_positive_cves = false_positive_cves ,
493+ not_affected_cves = not_affected_cves ,
494+ )
495+ )
435496
436- if "*" in product_info .vendor :
437- star_warn = "* vendors guessed by the tool"
497+ if "*" in product_info .vendor :
498+ star_warn = "* vendors guessed by the tool"
438499
439- # update all_paths
440- for path in product_paths :
441- all_paths [path ].append (hid )
500+ # update all_paths
501+ for path in product_paths :
502+ all_paths [path ].append (hid )
442503
443504 # Dashboard Rendering
444505 dashboard = dashboard .render (
@@ -451,6 +512,7 @@ def output_html(
451512 cve_remarks = cve_remarks ,
452513 cve_severity = cve_severity ,
453514 table_content = table_content ,
515+ no_scan = no_scan ,
454516 )
455517
456518 # try to load the bigger files just before the generation of report
0 commit comments