@@ -97,6 +97,7 @@ def output_html(
97
97
outfile ,
98
98
affected_versions : int = 0 ,
99
99
strip_scan_dir : bool = False ,
100
+ no_scan : bool = False ,
100
101
):
101
102
"""Returns a HTML report for CVE's"""
102
103
@@ -156,15 +157,27 @@ def output_html(
156
157
# Start generating graph with the data
157
158
158
159
# 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
+ )
168
181
169
182
# Chart configuration for product_pie
170
183
product_pie .update_layout (
@@ -183,46 +196,50 @@ def output_html(
183
196
184
197
# dash graph2: Product CVE's Graph
185
198
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 ,
210
235
)
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 ,
221
236
)
222
- )
223
237
224
238
# 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" )
226
243
227
244
all_paths = defaultdict (list )
228
245
@@ -240,56 +257,71 @@ def output_html(
240
257
cve_severity = {"CRITICAL" : 0 , "HIGH" : 0 , "MEDIUM" : 0 , "LOW" : 0 , "UNKNOWN" : 0 }
241
258
242
259
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
+ )
264
282
265
283
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 )
286
305
# Join the HTML rows to create the full table content
287
306
table_content = "\n " .join (cve_metric_html_rows )
288
307
289
308
# List of Products
290
309
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
293
325
# group product wise cves on the basis of remarks
294
326
cve_by_remark = group_cve_by_remark (cve_data ["cves" ])
295
327
@@ -304,13 +336,6 @@ def output_html(
304
336
norm_severity = normalize_severity (cve .severity )
305
337
cve_severity [norm_severity ] += 1
306
338
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
- )
314
339
new_cves = render_cves (
315
340
hid ,
316
341
cve_row ,
@@ -404,13 +429,6 @@ def output_html(
404
429
if not_affected_cves :
405
430
remarks += "not_affected "
406
431
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
-
414
432
products_found .append (
415
433
product_row .render (
416
434
vendor = product_info .vendor ,
@@ -432,13 +450,56 @@ def output_html(
432
450
not_affected_cves = not_affected_cves ,
433
451
)
434
452
)
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
+ )
435
496
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"
438
499
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 )
442
503
443
504
# Dashboard Rendering
444
505
dashboard = dashboard .render (
@@ -451,6 +512,7 @@ def output_html(
451
512
cve_remarks = cve_remarks ,
452
513
cve_severity = cve_severity ,
453
514
table_content = table_content ,
515
+ no_scan = no_scan ,
454
516
)
455
517
456
518
# try to load the bigger files just before the generation of report
0 commit comments