Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 105 additions & 1 deletion cve_bin_tool/output_engine/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from __future__ import annotations

import json
from collections import Counter, defaultdict
from datetime import datetime
from logging import Logger
Expand All @@ -14,7 +15,7 @@

from cve_bin_tool.merge import MergeReports

from ..util import CVEData, ProductInfo, Remarks, VersionInfo, strip_path
from ..util import CVE, CVEData, ProductInfo, Remarks, VersionInfo, strip_path
from ..version import VERSION
from .print_mode import html_print_mode
from .util import group_cve_by_remark
Expand Down Expand Up @@ -52,6 +53,94 @@ def normalize_severity(severity: str) -> str:
return "UNKNOWN"


def serialize_vulnerability_data(
all_cve_data: dict[ProductInfo, CVEData],
all_cve_version_info: dict[str, VersionInfo] | None,
scanned_dir: str,
total_files: int,
products_with_cve: int,
products_without_cve: int,
affected_versions: int = 0,
) -> str:
"""
Serialize all vulnerability data to JSON format for embedding in HTML.

Returns:
JSON string containing all vulnerability data in a structured format
"""
data = {
"metadata": {
"scanned_dir": scanned_dir,
"total_files": total_files,
"products_with_cve": products_with_cve,
"products_without_cve": products_without_cve,
"affected_versions": affected_versions,
"generated_at": datetime.now().isoformat(),
"tool_version": VERSION,
},
"products": [],
"vulnerabilities": {},
}

# Process each product and its CVEs
for product_info, cve_data in all_cve_data.items():
product_dict = {
"vendor": product_info.vendor,
"product": product_info.product,
"version": product_info.version,
"location": product_info.purl or "",
"purl": product_info.purl,
"paths": list(cve_data.get("paths", set())),
"cve_count": len(cve_data.get("cves", [])),
"cves": [],
}

# Process CVEs for this product
for cve in cve_data.get("cves", []):
if isinstance(cve, CVE):
cve_id = cve.cve_number

# Add CVE to product's CVE list
product_dict["cves"].append(cve_id)

# Add detailed CVE information to vulnerabilities dictionary
if cve_id not in data["vulnerabilities"]:
data["vulnerabilities"][cve_id] = {
"cve_number": cve.cve_number,
"severity": cve.severity,
"score": cve.score,
"cvss_version": cve.cvss_version,
"cvss_vector": cve.cvss_vector,
"description": cve.description,
"data_source": cve.data_source,
"last_modified": cve.last_modified,
"metric": cve.metric,
"remarks": (
cve.remarks.value
if hasattr(cve.remarks, "value")
else str(cve.remarks)
),
"comments": cve.comments,
"justification": cve.justification,
"response": cve.response,
}

data["products"].append(product_dict)

# Add version information if available
if all_cve_version_info:
data["version_info"] = {}
for cve_id, version_info in all_cve_version_info.items():
data["version_info"][cve_id] = {
"start_including": version_info.start_including,
"start_excluding": version_info.start_excluding,
"end_including": version_info.end_including,
"end_excluding": version_info.end_excluding,
}

return json.dumps(data, indent=2)


def render_cves(
hid: str, cve_row: Template, tag: str, cves: list[dict[str, str]]
) -> str:
Expand Down Expand Up @@ -466,10 +555,23 @@ def output_html(
js_main = "js/main.js"
js_bootstrap = "js/bootstrap.js"
js_plotly = "js/plotly.js"
js_triage = "js/triage.js"

script_main = templates_env.get_template(js_main)
script_bootstrap = templates_env.get_template(js_bootstrap)
script_plotly = templates_env.get_template(js_plotly)
script_triage = templates_env.get_template(js_triage)

# Generate vulnerability data JSON for interactive features
vulnerability_data_json = serialize_vulnerability_data(
all_cve_data,
all_cve_version_info,
scanned_dir,
total_files,
products_with_cve,
products_without_cve,
affected_versions,
)

# Render the base html to generate report
outfile.write(
Expand Down Expand Up @@ -500,6 +602,8 @@ def output_html(
script_main=script_main.render(),
script_bootstrap=script_bootstrap.render(),
script_plotly=script_plotly.render(),
script_triage=script_triage.render(),
vulnerability_data_json=vulnerability_data_json,
)
)

Expand Down
80 changes: 80 additions & 0 deletions cve_bin_tool/output_engine/html_reports/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,84 @@ a{
.btn-filter{
margin-top: 0px;
}
}

/* VEX Triage Specific Styles */
.vex-triage-controls {
border: 2px solid #dee2e6;
border-radius: 8px;
background-color: #f8f9fa;
transition: all 0.3s ease;
}

.vex-triage-controls:hover {
border-color: #007bff;
box-shadow: 0 4px 8px rgba(0, 123, 255, 0.1);
}

.triaged-item {
position: relative;
transition: all 0.3s ease;
}

.triaged-item::before {
content: '✓';
position: absolute;
left: -15px;
top: 50%;
transform: translateY(-50%);
color: #28a745;
font-weight: bold;
font-size: 1.2em;
}

.vex-justification-container {
transition: all 0.3s ease;
}

.vex-status-select:focus,
.vex-justification-select:focus,
.vex-detail-textarea:focus {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}

.triage-status-badge {
animation: fadeIn 0.3s ease-in;
}

@keyframes fadeIn {
from { opacity: 0; transform: scale(0.8); }
to { opacity: 1; transform: scale(1); }
}

/* VEX Status Badge Colors */
.bg-success { background-color: #28a745 !important; }
.bg-danger { background-color: #dc3545 !important; }
.bg-warning { background-color: #ffc107 !important; color: #212529 !important; }
.bg-info { background-color: #17a2b8 !important; }
.bg-secondary { background-color: #6c757d !important; }

/* Progress indicator for triage completion */
.triage-progress {
height: 4px;
background: linear-gradient(90deg, #28a745 0%, #ffc107 50%, #dc3545 100%);
border-radius: 2px;
margin-bottom: 1rem;
}

/* Responsive adjustments for VEX controls */
@media only screen and (max-width: 768px) {
.vex-triage-controls .row > .col-md-6 {
margin-bottom: 1rem;
}

.vex-triage-controls h6 {
font-size: 1.1rem;
}

.btn-group .btn-sm {
font-size: 0.8rem;
padding: 0.25rem 0.5rem;
}
}
Loading
Loading