#!/usr/bin/env python3 import os import datetime import mysql.connector from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.units import mm from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable from reportlab.lib.enums import TA_CENTER SEVERITY_COLORS = { "critical": "#c0392b", "high": "#e67e22", "medium": "#f1c40f", "low": "#27ae60", "unknown": "#7f8c8d", } RISK_COLORS = { "CRITICAL": "#c0392b", "HIGH": "#e67e22", "MEDIUM": "#f1c40f", "LOW": "#27ae60", "UNKNOWN": "#7f8c8d", } def get_connection(): return mysql.connector.connect( host="localhost", user="metatron", password="123", database="metatron" ) def fetch_session(sl_no: int) -> dict: conn = get_connection() c = conn.cursor() c.execute("SELECT * FROM history WHERE sl_no = %s", (sl_no,)) history = c.fetchone() c.execute("SELECT * FROM vulnerabilities WHERE sl_no = %s", (sl_no,)) vulns = c.fetchall() c.execute("SELECT * FROM fixes WHERE sl_no = %s", (sl_no,)) fixes = c.fetchall() c.execute("SELECT * FROM exploits_attempted WHERE sl_no = %s", (sl_no,)) exploits = c.fetchall() c.execute("SELECT * FROM summary WHERE sl_no = %s", (sl_no,)) summary = c.fetchone() conn.close() return {"history": history, "vulns": vulns, "fixes": fixes, "exploits": exploits, "summary": summary} def fetch_all_history(): conn = get_connection() c = conn.cursor() c.execute("SELECT sl_no, target, scan_date, status FROM history ORDER BY sl_no DESC") rows = c.fetchall() conn.close() return rows def export_pdf(data: dict, output_dir: str) -> str: h = data["history"] sl = h[0] tgt = h[1] date = str(h[2]) risk = data["summary"][4] if data["summary"] else "UNKNOWN" ai = data["summary"][3] if data["summary"] else "" os.makedirs(output_dir, exist_ok=True) safe = tgt.replace("https://","").replace("http://","").replace("/","_").replace(".","_") filename = os.path.join(output_dir, f"metatron_SL{sl}_{safe}.pdf") doc = SimpleDocTemplate(filename, pagesize=A4, topMargin=15*mm, bottomMargin=15*mm, leftMargin=15*mm, rightMargin=15*mm) title_style = ParagraphStyle("t", fontSize=22, fontName="Helvetica-Bold", textColor=colors.HexColor("#c0392b"), spaceAfter=4) sub_style = ParagraphStyle("s", fontSize=10, fontName="Helvetica", textColor=colors.HexColor("#555555"), spaceAfter=2) h1_style = ParagraphStyle("h1", fontSize=13, fontName="Helvetica-Bold", textColor=colors.HexColor("#2c3e50"), spaceBefore=10, spaceAfter=4) body_style = ParagraphStyle("b", fontSize=9, fontName="Helvetica", textColor=colors.black, leading=13) code_style = ParagraphStyle("c", fontSize=7.5, fontName="Courier", textColor=colors.HexColor("#2c3e50"), backColor=colors.HexColor("#f4f4f4"), leading=11, leftIndent=6, rightIndent=6, spaceBefore=2, spaceAfter=2) footer_style = ParagraphStyle("f", fontSize=7, textColor=colors.HexColor("#aaaaaa"), alignment=TA_CENTER) story = [] story.append(Paragraph("METATRON", title_style)) story.append(Paragraph("AI Penetration Testing Report", sub_style)) story.append(HRFlowable(width="100%", thickness=1.5, color=colors.HexColor("#c0392b"), spaceAfter=8)) risk_color = colors.HexColor(RISK_COLORS.get(risk.upper(), "#7f8c8d")) meta = [["Target", tgt], ["Scan Date", date], ["Session", f"SL# {sl}"], ["Risk Level", risk], ["Generated", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")]] mt = Table(meta, colWidths=[35*mm, 130*mm]) mt.setStyle(TableStyle([ ("FONTNAME", (0,0), (-1,-1), "Helvetica"), ("FONTSIZE", (0,0), (-1,-1), 9), ("FONTNAME", (0,0), (0,-1), "Helvetica-Bold"), ("TEXTCOLOR", (0,0), (0,-1), colors.HexColor("#2c3e50")), ("TEXTCOLOR", (1,3), (1,3), risk_color), ("FONTNAME", (1,3), (1,3), "Helvetica-Bold"), ("ROWBACKGROUNDS", (0,0), (-1,-1), [colors.HexColor("#f9f9f9"), colors.white]), ("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#dddddd")), ("PADDING", (0,0), (-1,-1), 5), ])) story.append(mt) story.append(Spacer(1, 10)) story.append(Paragraph("Vulnerabilities", h1_style)) story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#dddddd"), spaceAfter=6)) if data["vulns"]: vd = [["#", "Vulnerability", "Severity", "Port", "Service"]] for v in data["vulns"]: vd.append([str(v[0]), str(v[2] or "-"), str(v[3] or "-").upper(), str(v[4] or "-"), str(v[5] or "-")]) vt = Table(vd, colWidths=[10*mm, 72*mm, 24*mm, 18*mm, 28*mm], repeatRows=1) vts = [ ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"), ("FONTSIZE", (0,0), (-1,-1), 8), ("BACKGROUND", (0,0), (-1,0), colors.HexColor("#2c3e50")), ("TEXTCOLOR", (0,0), (-1,0), colors.white), ("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#dddddd")), ("PADDING", (0,0), (-1,-1), 5), ("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.HexColor("#f9f9f9"), colors.white]), ] for i, v in enumerate(data["vulns"], 1): sc = colors.HexColor(SEVERITY_COLORS.get((v[3] or "unknown").lower(), "#7f8c8d")) vts.append(("TEXTCOLOR", (2,i), (2,i), sc)) vts.append(("FONTNAME", (2,i), (2,i), "Helvetica-Bold")) vt.setStyle(TableStyle(vts)) story.append(vt) story.append(Spacer(1, 6)) story.append(Paragraph("Vulnerability Details", h1_style)) story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#dddddd"), spaceAfter=6)) for v in data["vulns"]: sc = colors.HexColor(SEVERITY_COLORS.get((v[3] or "unknown").lower(), "#7f8c8d")) lbl = ParagraphStyle("vl", fontSize=9, fontName="Helvetica-Bold", textColor=sc) story.append(Paragraph(f"[{(v[3] or 'UNKNOWN').upper()}] {v[2]}", lbl)) if v[6]: story.append(Paragraph(str(v[6]), body_style)) story.append(Spacer(1, 4)) else: story.append(Paragraph("No vulnerabilities recorded.", body_style)) story.append(Spacer(1, 6)) story.append(Paragraph("Fixes & Mitigations", h1_style)) story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#dddddd"), spaceAfter=6)) if data["fixes"]: for f in data["fixes"]: story.append(Paragraph(f"Fix for vuln id={f[2]}:", body_style)) story.append(Paragraph(str(f[3] or "-"), code_style)) story.append(Spacer(1, 3)) else: story.append(Paragraph("No fixes recorded.", body_style)) story.append(Spacer(1, 6)) story.append(Paragraph("Exploits Attempted", h1_style)) story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#dddddd"), spaceAfter=6)) if data["exploits"]: ed = [["#", "Exploit", "Tool", "Result"]] for e in data["exploits"]: ed.append([str(e[0]), str(e[2] or "-")[:60], str(e[3] or "-")[:30], str(e[5] or "-")[:30]]) et = Table(ed, colWidths=[10*mm, 80*mm, 40*mm, 28*mm]) et.setStyle(TableStyle([ ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"), ("FONTSIZE", (0,0), (-1,-1), 8), ("BACKGROUND", (0,0), (-1,0), colors.HexColor("#2c3e50")), ("TEXTCOLOR", (0,0), (-1,0), colors.white), ("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#dddddd")), ("PADDING", (0,0), (-1,-1), 5), ("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.HexColor("#f9f9f9"), colors.white]), ])) story.append(et) else: story.append(Paragraph("No exploits recorded.", body_style)) story.append(Spacer(1, 6)) story.append(Paragraph("AI Analysis Summary", h1_style)) story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#dddddd"), spaceAfter=6)) if ai: for line in str(ai).split("\n"): line = line.strip() if line: story.append(Paragraph(line, body_style)) story.append(Spacer(1, 2)) else: story.append(Paragraph("No AI analysis recorded.", body_style)) story.append(Spacer(1, 10)) story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#dddddd"), spaceAfter=4)) story.append(Paragraph( "Generated by METATRON — AI Penetration Testing Assistant | " "github.com/sooryathejas/METATRON | For authorized use only.", footer_style)) doc.build(story) return filename def export_html(data: dict, output_dir: str) -> str: h = data["history"] sl = h[0] tgt = h[1] date = str(h[2]) risk = data["summary"][4] if data["summary"] else "UNKNOWN" ai = data["summary"][3] if data["summary"] else "" rc = RISK_COLORS.get(risk.upper(), "#7f8c8d") os.makedirs(output_dir, exist_ok=True) safe = tgt.replace("https://","").replace("http://","").replace("/","_").replace(".","_") filename = os.path.join(output_dir, f"metatron_SL{sl}_{safe}.html") vuln_rows = "" for v in data["vulns"]: sc = SEVERITY_COLORS.get((v[3] or "unknown").lower(), "#7f8c8d") vuln_rows += (f"{v[0]}" f"{v[2]}
{v[6] or ''}" f"" f"{(v[3] or 'unknown').upper()}" f"{v[4] or '-'}{v[5] or '-'}") fix_rows = "" for f in data["fixes"]: fix_rows += (f"{f[0]}vuln #{f[2]}" f"{f[3] or '-'}" f"{f[4] or 'ai'}") exp_rows = "" for e in data["exploits"]: exp_rows += (f"{e[0]}{e[2] or '-'}" f"{e[3] or '-'}" f"{str(e[4] or '-')[:80]}" f"{e[5] or '-'}") ai_html = "".join(f"

{line}

" for line in str(ai).split("\n") if line.strip()) html = f""" Metatron Report — {tgt}

🔱 METATRON

AI Penetration Testing Report

Target
{tgt}
Session
SL# {sl}
Scan Date
{date}
Risk Level
{risk}

Vulnerabilities

{'' + vuln_rows + '
#VulnerabilitySeverityPortService
' if data["vulns"] else '

None recorded.

'}

Fixes & Mitigations

{'' + fix_rows + '
#VulnFixSource
' if data["fixes"] else '

None recorded.

'}

Exploits Attempted

{'' + exp_rows + '
#ExploitToolPayloadResult
' if data["exploits"] else '

None recorded.

'}

AI Analysis Summary

{ai_html if ai_html else '

None recorded.

'}
""" with open(filename, "w") as f: f.write(html) return filename def export_menu(data: dict): if not data["history"]: print("[!] No session data to export.") return h = data["history"] sl = h[0] tgt = h[1] print(f"\n\033[33m{'─'*20} EXPORT SL#{sl} — {tgt} {'─'*20}\033[0m") print(" [1] PDF report") print(" [2] HTML report") print(" [3] Both") print(" [4] Back") print(f"\033[90m{'─'*60}\033[0m") choice = input("\033[36mExport format: \033[0m").strip() output_dir = os.path.expanduser("~/METATRON/reports") os.makedirs(output_dir, exist_ok=True) if choice == "1": p = export_pdf(data, output_dir) print(f"\033[92m[+] PDF saved: {p}\033[0m") elif choice == "2": p = export_html(data, output_dir) print(f"\033[92m[+] HTML saved: {p}\033[0m") elif choice == "3": p1 = export_pdf(data, output_dir) p2 = export_html(data, output_dir) print(f"\033[92m[+] PDF : {p1}\033[0m") print(f"\033[92m[+] HTML : {p2}\033[0m") elif choice == "4": return else: print("\033[93m[!] Invalid choice.\033[0m") if __name__ == "__main__": print("\n\033[91m METATRON — Standalone Report Exporter\033[0m") print("\033[90m ─────────────────────────────────────\033[0m\n") rows = fetch_all_history() if not rows: print("[!] No sessions found in database.") exit() print(f"{'SL#':<6} {'TARGET':<28} {'DATE':<22} {'STATUS'}") print("─" * 65) for row in rows: print(f"{row[0]:<6} {row[1]:<28} {str(row[2]):<22} {row[3]}") print() sl_input = input("\033[36mEnter SL# to export: \033[0m").strip() if not sl_input.isdigit(): print("[!] Invalid SL#.") exit() data = fetch_session(int(sl_input)) if not data["history"]: print(f"[!] SL# {sl_input} not found.") exit() export_menu(data)