"""Bugcrowd report builder — auto-draft submissions from findings. Generates structured reports in Bugcrowd format with evidence links, severity justification, and remediation suggestions. """ from datetime import datetime from typing import Any, Dict, List from infj_bot.core.plugins.findings_db import Finding SEVERITY_MAP = { "P1": "critical", "P2": "high", "P3": "medium", "P4": "low", "P5": "informational", } class ReportBuilder: """Build Bugcrowd-ready reports from findings.""" def build(self, finding: Finding, evidence_list: List[Dict[str, Any]]) -> str: """Return a full Bugcrowd markdown report.""" lines = [ f"# {finding.title}", "", f"**Severity:** {finding.severity} ({SEVERITY_MAP.get(finding.severity, 'unknown')})", f"**Asset:** {finding.asset or 'N/A'}", f"**Confidence:** {finding.confidence}", f"**Vulnerability Type:** {finding.vuln_type or 'TBD'}", "", "## Summary", finding.description or "_No description provided._", "", "## Impact", finding.impact or "_Impact not yet assessed._", "", "## Steps to Reproduce", finding.reproduction or "1. _(reproduction steps not yet documented)_", "", "## Evidence", ] if evidence_list: for ev in evidence_list: lines.append( f"- **{ev.get('type', 'file')}**: `{ev.get('path', 'N/A')}` — {ev.get('description', '')}" ) else: lines.append("_No evidence attached yet._") lines.extend( [ "", "## Suggested Fix", finding.fix or "_No fix suggested yet._", "", "## Notes", finding.notes or "_None._", "", f"---\n*Report generated by DRIFT Bug Bot at {datetime.now().isoformat(timespec='seconds')}*", ] ) return "\n".join(lines) def build_summary(self, findings: List[Finding]) -> str: """Build a summary table of multiple findings.""" if not findings: return "No findings to summarize." lines = [ "# Bug Hunt Summary", "", "| ID | Title | Severity | Status | Asset |", "|----|-------|----------|--------|-------|", ] for f in findings: title = (f.title[:40] + "...") if len(f.title) > 40 else f.title asset = ( (f.asset[:30] + "...") if f.asset and len(f.asset) > 30 else (f.asset or "N/A") ) lines.append(f"| {f.id} | {title} | {f.severity} | {f.status} | {asset} |") return "\n".join(lines) def severity_justification(self, severity: str, impact: str) -> str: """Return a pre-written severity justification block.""" justifications = { "P1": ( "**P1 — Critical**\n" "- Direct compromise of confidentiality, integrity, or availability.\n" "- No user interaction required or trivial to trigger.\n" "- Affects production data or core infrastructure." ), "P2": ( "**P2 — High**\n" "- Significant security impact with limited prerequisites.\n" "- May require authenticated access or specific conditions.\n" "- Could lead to data exposure or unauthorized actions." ), "P3": ( "**P3 — Medium**\n" "- Noticeable security weakness with moderate exploit complexity.\n" "- Requires multiple steps or specific context to abuse.\n" "- Impact is contained or requires chaining with other issues." ), "P4": ( "**P4 — Low**\n" "- Minor security concern or best-practice deviation.\n" "- Exploitation is difficult or impact is negligible.\n" "- Defense-in-depth issue rather than direct vulnerability." ), "P5": ( "**P5 — Informational**\n" "- No direct security impact.\n" "- Useful for hardening or awareness.\n" "- Sandbox-only or documentation-level finding." ), } return justifications.get(severity, "Severity not classified.")