Implements a comprehensive system for uploading and processing weekly vulnerability reports that automatically splits multiple CVE IDs in a single cell into separate rows for easier filtering and analysis. Backend Changes: - Add weekly_reports table with migration - Create Excel processor helper using Python child_process - Implement API routes for upload, list, download, delete - Mount routes in server.js after multer initialization - Move split_cve_report.py to backend/scripts/ Frontend Changes: - Add WeeklyReportModal component with phase-based UI - Add "Weekly Report" button next to NVD Sync - Integrate modal into App.js with state management - Display existing reports with current report indicator - Download buttons for original and processed files Features: - Upload .xlsx files (editor/admin only) - Automatic CVE ID splitting via Python script - Store metadata in database + files on filesystem - Auto-archive previous reports (mark one as current) - Download both original and processed versions - Audit logging for all operations - Security: file validation, auth checks, path sanitization Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
84 lines
2.7 KiB
Python
Executable File
84 lines
2.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
CVE Report Splitter
|
|
Splits multiple CVE IDs in a single row into separate rows for easier filtering and analysis.
|
|
"""
|
|
|
|
import pandas as pd
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
def split_cve_report(input_file, output_file=None, sheet_name='Vulnerabilities', cve_column='CVE ID'):
|
|
"""
|
|
Split CVE IDs into separate rows.
|
|
|
|
Args:
|
|
input_file: Path to input Excel file
|
|
output_file: Path to output file (default: adds '_Split' to input filename)
|
|
sheet_name: Name of sheet with vulnerability data (default: 'Vulnerabilities')
|
|
cve_column: Name of column containing CVE IDs (default: 'CVE ID')
|
|
"""
|
|
input_path = Path(input_file)
|
|
|
|
if not input_path.exists():
|
|
print(f"Error: File not found: {input_file}")
|
|
sys.exit(1)
|
|
|
|
if output_file is None:
|
|
output_file = input_path.parent / f"{input_path.stem}_Split{input_path.suffix}"
|
|
|
|
print(f"Reading: {input_file}")
|
|
|
|
try:
|
|
df = pd.read_excel(input_file, sheet_name=sheet_name)
|
|
except ValueError as e:
|
|
print(f"Error: Sheet '{sheet_name}' not found in workbook")
|
|
print(f"Available sheets: {pd.ExcelFile(input_file).sheet_names}")
|
|
sys.exit(1)
|
|
|
|
if cve_column not in df.columns:
|
|
print(f"Error: Column '{cve_column}' not found")
|
|
print(f"Available columns: {list(df.columns)}")
|
|
sys.exit(1)
|
|
|
|
original_rows = len(df)
|
|
print(f"Original rows: {original_rows}")
|
|
|
|
# Split CVE IDs by comma
|
|
df[cve_column] = df[cve_column].astype(str).str.split(',')
|
|
|
|
# Explode to create separate rows
|
|
df_exploded = df.explode(cve_column)
|
|
|
|
# Clean up CVE IDs
|
|
df_exploded[cve_column] = df_exploded[cve_column].str.strip()
|
|
df_exploded = df_exploded[df_exploded[cve_column].notna()]
|
|
df_exploded = df_exploded[df_exploded[cve_column] != 'nan']
|
|
df_exploded = df_exploded[df_exploded[cve_column] != '']
|
|
|
|
# Reset index
|
|
df_exploded = df_exploded.reset_index(drop=True)
|
|
|
|
new_rows = len(df_exploded)
|
|
print(f"New rows: {new_rows}")
|
|
print(f"Added {new_rows - original_rows} rows from splitting CVEs")
|
|
|
|
# Save output
|
|
df_exploded.to_excel(output_file, index=False, sheet_name=sheet_name)
|
|
print(f"\n✓ Success! Saved to: {output_file}")
|
|
|
|
return output_file
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python3 split_cve_report.py <input_file.xlsx> [output_file.xlsx]")
|
|
print("\nExample:")
|
|
print(" python3 split_cve_report.py 'Vulnerability Workbook.xlsx'")
|
|
print(" python3 split_cve_report.py 'input.xlsx' 'output.xlsx'")
|
|
sys.exit(1)
|
|
|
|
input_file = sys.argv[1]
|
|
output_file = sys.argv[2] if len(sys.argv) > 2 else None
|
|
|
|
split_cve_report(input_file, output_file)
|