diff --git a/cli/securelens/output/pdf.py b/cli/securelens/output/pdf.py new file mode 100644 index 0000000..3677e40 --- /dev/null +++ b/cli/securelens/output/pdf.py @@ -0,0 +1,185 @@ +from fpdf import FPDF +import datetime +from typing import Optional + +class SecureLensPDF(FPDF): + def footer(self): + self.set_y(-15) + self.set_font("helvetica", "I", 8) + self.set_text_color(128, 128, 128) + self.cell(0, 10, f"Page {self.page_no()}", align="C") + + +def export_code_pdf(result, output_path: str) -> str: + """ + Generates a PDF report for a local codebase scan. + """ + pdf = SecureLensPDF() + pdf.add_page() + + # Header section + pdf.set_font("helvetica", "B", 18) + pdf.set_text_color(20, 40, 80) + pdf.cell(0, 15, "SecureLens AI - Codebase Security Audit", new_x="LMARGIN", new_y="NEXT", align="C") + + # Metadata + pdf.set_font("helvetica", "", 10) + pdf.set_text_color(100, 100, 100) + now_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + pdf.cell(0, 8, f"Target Path: {result.target}", new_x="LMARGIN", new_y="NEXT") + pdf.cell(0, 8, f"Scan Time: {now_str}", new_x="LMARGIN", new_y="NEXT") + pdf.cell(0, 8, f"Security Score: {result.score}/100 (Grade: {result.grade})", new_x="LMARGIN", new_y="NEXT") + pdf.ln(5) + + # Executive Summary Section + pdf.set_font("helvetica", "B", 13) + pdf.set_text_color(20, 40, 80) + pdf.set_fill_color(240, 240, 240) + pdf.cell(0, 10, " Executive Summary", new_x="LMARGIN", new_y="NEXT", fill=True) + pdf.set_font("helvetica", "", 10) + pdf.set_text_color(0, 0, 0) + pdf.ln(2) + summary_text = result.ai_summary or f"A static patterns analysis was performed on the codebase. Out of the files discovered, {len(result.vulnerabilities)} potential security vulnerabilities were reported." + pdf.multi_cell(0, 5, summary_text) + pdf.ln(8) + + # Files Scanned Section + pdf.set_font("helvetica", "B", 13) + pdf.set_text_color(20, 40, 80) + pdf.cell(0, 10, " Files Analyzed", new_x="LMARGIN", new_y="NEXT", fill=True) + pdf.set_font("helvetica", "", 10) + pdf.set_text_color(0, 0, 0) + pdf.ln(2) + files_list = ", ".join(result.files_triaged[:15]) + if len(result.files_triaged) > 15: + files_list += f", and {len(result.files_triaged) - 15} more" + pdf.multi_cell(0, 5, files_list or "No files selected.") + pdf.ln(8) + + # Issues Findings Section + pdf.set_font("helvetica", "B", 13) + pdf.set_text_color(20, 40, 80) + pdf.cell(0, 10, " Security Findings", new_x="LMARGIN", new_y="NEXT", fill=True) + pdf.ln(5) + + if not result.vulnerabilities: + pdf.set_font("helvetica", "I", 11) + pdf.set_text_color(0, 100, 0) + pdf.cell(0, 10, "No security vulnerabilities were identified in the scanned files.", new_x="LMARGIN", new_y="NEXT") + else: + for idx, v in enumerate(result.vulnerabilities, 1): + severity = v.severity + pdf.set_font("helvetica", "B", 11) + + # Severity color coding + if severity == "Critical": pdf.set_text_color(200, 0, 0) + elif severity == "High": pdf.set_text_color(255, 69, 0) + elif severity == "Medium": pdf.set_text_color(218, 165, 32) + else: pdf.set_text_color(0, 100, 0) + + line_str = f" [Line {v.line_number}]" if v.line_number else "" + pdf.cell(0, 8, f"{idx}. {severity}: {v.issue}{line_str}", new_x="LMARGIN", new_y="NEXT") + + # Details + pdf.set_text_color(0, 0, 0) + pdf.set_font("helvetica", "B", 9) + pdf.cell(20, 6, "File:", border=0) + pdf.set_font("helvetica", "", 9) + pdf.cell(0, 6, v.file_path, new_x="LMARGIN", new_y="NEXT") + + pdf.set_font("helvetica", "B", 9) + pdf.cell(0, 6, "Explanation:", new_x="LMARGIN", new_y="NEXT") + pdf.set_font("helvetica", "", 9) + pdf.multi_cell(0, 4.5, v.explanation) + + if v.suggested_fix: + pdf.set_font("helvetica", "B", 9) + pdf.cell(0, 6, "Suggested Fix:", new_x="LMARGIN", new_y="NEXT") + pdf.set_font("courier", "", 8.5) + pdf.set_fill_color(245, 245, 245) + pdf.multi_cell(0, 4.5, v.suggested_fix, fill=True) + + pdf.ln(4) + pdf.line(pdf.get_x(), pdf.get_y(), 200, pdf.get_y()) + pdf.ln(3) + + pdf.output(output_path) + return output_path + + +def export_web_pdf(result, output_path: str) -> str: + """ + Generates a PDF report for a live URL scan. + """ + pdf = SecureLensPDF() + pdf.add_page() + + # Header section + pdf.set_font("helvetica", "B", 18) + pdf.set_text_color(20, 40, 80) + pdf.cell(0, 15, "SecureLens AI - Website Security Audit", new_x="LMARGIN", new_y="NEXT", align="C") + + # Metadata + pdf.set_font("helvetica", "", 10) + pdf.set_text_color(100, 100, 100) + now_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + pdf.cell(0, 8, f"Target URL: {result.url}", new_x="LMARGIN", new_y="NEXT") + pdf.cell(0, 8, f"Scan Time: {now_str}", new_x="LMARGIN", new_y="NEXT") + pdf.cell(0, 8, f"Security Score: {result.score}/100 (Grade: {result.grade})", new_x="LMARGIN", new_y="NEXT") + if result.ssl_expiry_days is not None: + pdf.cell(0, 8, f"SSL Expiry: {result.ssl_expiry_days} days left", new_x="LMARGIN", new_y="NEXT") + pdf.ln(5) + + # Executive Summary Section + pdf.set_font("helvetica", "B", 13) + pdf.set_text_color(20, 40, 80) + pdf.set_fill_color(240, 240, 240) + pdf.cell(0, 10, " Executive Summary", new_x="LMARGIN", new_y="NEXT", fill=True) + pdf.set_font("helvetica", "", 10) + pdf.set_text_color(0, 0, 0) + pdf.ln(2) + summary_text = result.ai_summary or f"An automated live security audit was performed on {result.url}. Out of the layers checked, {len(result.issues)} potential issues were flagged." + pdf.multi_cell(0, 5, summary_text) + pdf.ln(8) + + # Issues Section + pdf.set_font("helvetica", "B", 13) + pdf.set_text_color(20, 40, 80) + pdf.cell(0, 10, " Discovered Security Issues", new_x="LMARGIN", new_y="NEXT", fill=True) + pdf.ln(5) + + if not result.issues: + pdf.set_font("helvetica", "I", 11) + pdf.set_text_color(0, 100, 0) + pdf.cell(0, 10, "No security issues were identified during the URL scan.", new_x="LMARGIN", new_y="NEXT") + else: + for idx, i in enumerate(result.issues, 1): + severity = i.severity + pdf.set_font("helvetica", "B", 11) + + # Severity color coding + if severity == "Critical": pdf.set_text_color(200, 0, 0) + elif severity == "Warning": pdf.set_text_color(218, 165, 32) + else: pdf.set_text_color(0, 100, 0) + + pdf.cell(0, 8, f"{idx}. {severity}: {i.issue}", new_x="LMARGIN", new_y="NEXT") + + # Details + pdf.set_text_color(0, 0, 0) + pdf.set_font("helvetica", "B", 9) + pdf.cell(20, 6, "Layer:", border=0) + pdf.set_font("helvetica", "", 9) + pdf.cell(0, 6, i.layer, new_x="LMARGIN", new_y="NEXT") + + pdf.set_font("helvetica", "B", 9) + pdf.cell(0, 6, "Remediation / Fix:", new_x="LMARGIN", new_y="NEXT") + pdf.set_font("courier", "", 8.5) + pdf.set_fill_color(245, 245, 245) + pdf.multi_cell(0, 4.5, i.fix, fill=True) + + pdf.ln(4) + pdf.line(pdf.get_x(), pdf.get_y(), 200, pdf.get_y()) + pdf.ln(3) + + pdf.output(output_path) + return output_path