Skip to content

Reporting

Report generation (stage 8) produces client-facing deliverables when a scan completes: a full PDF report, a one-page executive summary, and takedown email drafts. Delivery is automatic — the platform generates and serves reports, and can push them to a configured webhook.

reports/pdf_report.py (WeasyPrint + Jinja2) renders two documents from the scan’s audit data:

  • Full client report — every finding with enrichment, scoring, AI analysis, campaign analysis, and takedown contacts.
  • Executive summary — one page covering the executive summary, priority actions, severity distribution, campaign analysis, threat-feed exposure, and takedown contacts.
  1. On scan completion, Rails enqueues ReportGenerationJob.
  2. The job calls Python POST /v1/report/generate (180s read timeout), which returns base64 {full, summary} PDFs.
  3. Both PDFs are attached to the ScanReport via Active Storage; status becomes ready.
  4. A report_generated audit event is recorded.
  5. If the user has a webhook_url, WebhookDeliveryJob delivers the PDFs.

Reports are served inline at GET /reports/:scan_id (?variant=summary for the executive summary).

Report generation never blocks on a model call. If the AI provider is unavailable, the AI sections fall back to deterministic, finding-specific content (score, severity, phishing verdict, threat feeds, risk criteria, registrar, hosting IP), and a warning is logged (report_ai_deterministic_fallback). The report is always produced.

The takedown workflow drafts abuse-report emails for confirmed findings:

  1. From the dashboard or triage queue, an analyst triggers Generate takedown.
  2. Rails calls Python POST /v1/takedown/generate with {scan_id, finding_id, domain}.
  3. Python returns {subject, body} — a deterministic abuse-email template optionally polished by the LLM.
  4. Rails records a takedown_generated audit event and shows the draft with copy buttons.

See Takedowns.

Completion webhooks carry the full scan snapshot plus the generated PDFs (base64) to the user’s webhook_url. Delivery is best-effort and never raises. See Webhooks.

After a scan, the dashboard can request LLM-written narratives for each infrastructure campaign via POST /v1/campaign/narratives. The campaign_narratives Stimulus controller swaps the deterministic narrative for the LLM text when available.