|
import json |
|
import os |
|
from datetime import datetime |
|
from typing import Dict, List, Optional |
|
from smolagents import Tool |
|
import plotly.graph_objects as go |
|
import plotly.express as px |
|
from jinja2 import Template |
|
|
|
class ReportGeneratorTool(Tool): |
|
"""Tool for generating interactive HTML vulnerability reports with charts.""" |
|
|
|
name = "generate_vulnerability_report" |
|
description = "Generates an interactive HTML report with charts and vulnerability analysis. The report is generated from CVEDB search results." |
|
inputs = { |
|
"vulnerability_data": { |
|
"type": "string", |
|
"description": "Vulnerability data in JSON format", |
|
}, |
|
"report_type": { |
|
"type": "string", |
|
"description": "Report type: 'cve' for a specific CVE or 'product' for a product", |
|
} |
|
} |
|
output_type = "string" |
|
|
|
def __init__(self): |
|
super().__init__() |
|
|
|
|
|
self.html_template = """ |
|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<title>Vulnerability Report</title> |
|
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> |
|
<style> |
|
body { font-family: Arial, sans-serif; margin: 20px; } |
|
.container { max-width: 1200px; margin: 0 auto; } |
|
.header { text-align: center; margin-bottom: 30px; } |
|
.section { margin-bottom: 40px; } |
|
.chart { margin: 20px 0; } |
|
.summary { background-color: #f5f5f5; padding: 20px; border-radius: 5px; } |
|
.critical { color: #dc3545; } |
|
.high { color: #fd7e14; } |
|
.medium { color: #ffc107; } |
|
.low { color: #28a745; } |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<div class="header"> |
|
<h1>Vulnerability Report</h1> |
|
<p>Generated on {{ generation_date }}</p> |
|
</div> |
|
|
|
<div class="section"> |
|
<h2>Summary</h2> |
|
<div class="summary"> |
|
{{ summary }} |
|
</div> |
|
</div> |
|
|
|
<div class="section"> |
|
<h2>Severity Distribution (CVSS)</h2> |
|
<div id="cvss_chart" class="chart"></div> |
|
</div> |
|
|
|
<div class="section"> |
|
<h2>Temporal Trend</h2> |
|
<div id="timeline_chart" class="chart"></div> |
|
</div> |
|
|
|
<div class="section"> |
|
<h2>Vulnerability Details</h2> |
|
{{ vulnerabilities_table }} |
|
</div> |
|
</div> |
|
|
|
<script> |
|
{{ plotly_js }} |
|
</script> |
|
</body> |
|
</html> |
|
""" |
|
|
|
def forward(self, vulnerability_data: str, report_type: str) -> str: |
|
"""Generates an HTML report with interactive charts from vulnerability data.""" |
|
try: |
|
data = json.loads(vulnerability_data) |
|
|
|
|
|
cvss_chart = self._generate_cvss_chart(data) |
|
timeline_chart = self._generate_timeline_chart(data) |
|
|
|
|
|
vulnerabilities_table = self._generate_vulnerabilities_table(data) |
|
|
|
|
|
summary = self._generate_summary(data, report_type) |
|
|
|
|
|
template = Template(self.html_template) |
|
html = template.render( |
|
generation_date=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), |
|
summary=summary, |
|
vulnerabilities_table=vulnerabilities_table, |
|
plotly_js=f""" |
|
var cvssData = {cvss_chart}; |
|
var timelineData = {timeline_chart}; |
|
Plotly.newPlot('cvss_chart', cvssData.data, cvssData.layout); |
|
Plotly.newPlot('timeline_chart', timelineData.data, timelineData.layout); |
|
""" |
|
) |
|
|
|
|
|
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|
filename = f"vulnerability_report_{report_type}_{timestamp}.html" |
|
|
|
|
|
reports_dir = "reports" |
|
if not os.path.exists(reports_dir): |
|
os.makedirs(reports_dir) |
|
|
|
|
|
filepath = os.path.join(reports_dir, filename) |
|
with open(filepath, 'w', encoding='utf-8') as f: |
|
f.write(html) |
|
|
|
return f"Report generated and saved as: {filepath}\n\n{html}" |
|
|
|
except Exception as e: |
|
return f"Error generating report: {str(e)}" |
|
|
|
def _generate_cvss_chart(self, data: Dict) -> Dict: |
|
"""Generates a CVSS score distribution chart.""" |
|
if isinstance(data, list): |
|
cvss_scores = [v.get('cvss', 0) for v in data if 'cvss' in v] |
|
else: |
|
cvss_scores = [data.get('cvss', 0)] if 'cvss' in data else [] |
|
|
|
fig = go.Figure() |
|
fig.add_trace(go.Histogram( |
|
x=cvss_scores, |
|
nbinsx=10, |
|
name='CVSS Scores' |
|
)) |
|
|
|
fig.update_layout( |
|
title='CVSS Score Distribution', |
|
xaxis_title='CVSS Score', |
|
yaxis_title='Number of Vulnerabilities', |
|
showlegend=False |
|
) |
|
|
|
return fig.to_json() |
|
|
|
def _generate_timeline_chart(self, data: Dict) -> Dict: |
|
"""Generates a vulnerability timeline chart.""" |
|
if isinstance(data, list): |
|
dates = [v.get('published_time', '') for v in data if 'published_time' in v] |
|
else: |
|
dates = [data.get('published_time', '')] if 'published_time' in data else [] |
|
|
|
|
|
from collections import Counter |
|
from datetime import datetime |
|
|
|
date_counts = Counter() |
|
for date_str in dates: |
|
try: |
|
date = datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S") |
|
month_key = date.strftime("%Y-%m") |
|
date_counts[month_key] += 1 |
|
except: |
|
continue |
|
|
|
months = sorted(date_counts.keys()) |
|
counts = [date_counts[m] for m in months] |
|
|
|
fig = go.Figure() |
|
fig.add_trace(go.Scatter( |
|
x=months, |
|
y=counts, |
|
mode='lines+markers', |
|
name='Vulnerabilities' |
|
)) |
|
|
|
fig.update_layout( |
|
title='Vulnerability Timeline Trend', |
|
xaxis_title='Month', |
|
yaxis_title='Number of Vulnerabilities', |
|
showlegend=False |
|
) |
|
|
|
return fig.to_json() |
|
|
|
def _generate_vulnerabilities_table(self, data: Dict) -> str: |
|
"""Generates an HTML table with vulnerability details.""" |
|
if isinstance(data, list): |
|
vulnerabilities = data |
|
else: |
|
vulnerabilities = [data] |
|
|
|
if not vulnerabilities: |
|
return "<p>No vulnerability data available.</p>" |
|
|
|
table_html = """ |
|
<table border="1" style="width: 100%; border-collapse: collapse;"> |
|
<thead> |
|
<tr style="background-color: #f2f2f2;"> |
|
<th style="padding: 8px; text-align: left;">CVE ID</th> |
|
<th style="padding: 8px; text-align: left;">CVSS Score</th> |
|
<th style="padding: 8px; text-align: left;">EPSS Score</th> |
|
<th style="padding: 8px; text-align: left;">Known Exploitable</th> |
|
<th style="padding: 8px; text-align: left;">Publication Date</th> |
|
<th style="padding: 8px; text-align: left;">Summary</th> |
|
</tr> |
|
</thead> |
|
<tbody> |
|
""" |
|
|
|
for vuln in vulnerabilities: |
|
cvss = vuln.get('cvss', 'Not available') |
|
epss = vuln.get('epss', 'Not available') |
|
kev = vuln.get('kev', False) |
|
|
|
|
|
risk_class = "" |
|
if cvss != 'Not available' and isinstance(cvss, (int, float)): |
|
if cvss >= 7.0: |
|
risk_class = "critical" |
|
elif cvss >= 4.0: |
|
risk_class = "high" |
|
else: |
|
risk_class = "low" |
|
|
|
table_html += f""" |
|
<tr class="{risk_class}"> |
|
<td style="padding: 8px;">{vuln.get('id', 'Not available')}</td> |
|
<td style="padding: 8px;">{cvss}</td> |
|
<td style="padding: 8px;">{epss}</td> |
|
<td style="padding: 8px;">{'Yes' if kev else 'No'}</td> |
|
<td style="padding: 8px;">{vuln.get('published_time', 'Not available')}</td> |
|
<td style="padding: 8px;">{vuln.get('summary', 'Not available')[:100]}...</td> |
|
</tr> |
|
""" |
|
|
|
table_html += """ |
|
</tbody> |
|
</table> |
|
""" |
|
|
|
return table_html |
|
|
|
def _generate_summary(self, data: Dict, report_type: str) -> str: |
|
"""Generates a summary of the vulnerability data.""" |
|
if isinstance(data, list): |
|
total_vulns = len(data) |
|
exploited = sum(1 for v in data if v.get('kev', False)) |
|
avg_cvss = sum(v.get('cvss', 0) for v in data if 'cvss' in v) / max(1, sum(1 for v in data if 'cvss' in v)) |
|
else: |
|
total_vulns = 1 |
|
exploited = 1 if data.get('kev', False) else 0 |
|
avg_cvss = data.get('cvss', 0) |
|
|
|
summary_html = f""" |
|
<p>Found <strong>{total_vulns}</strong> vulnerabilities.</p> |
|
<p>Exploited vulnerabilities: <strong>{exploited}</strong></p> |
|
<p>Average CVSS Score: <strong>{avg_cvss:.2f}</strong></p> |
|
<p>Publication date: <strong>{data.get('published_time', 'N/A')}</strong></p> |
|
""" |
|
|
|
return summary_html |