strike / src /report_generator.py
imperat0's picture
Projeto
73c5503
Raw History Blame Contribute Delete
4.43 kB
from datetime import datetime
from collections import Counter
from typing import List
from threat_kb import get_threats_for_component
from detector import Detection
SEVERIDADE_ORDEM = {"Crítica": 0, "Alta": 1, "Média": 2, "Baixa": 3}
def _dedup_components(detections: List[Detection]) -> Counter:
"""Conta ocorrências únicas de cada tipo de componente (ignora bounding boxes duplicadas)."""
return Counter(d.label for d in detections)
def build_markdown_report(detections: List[Detection], demo_mode: bool = False,
sistema_nome: str = "Sistema Analisado") -> str:
counts = _dedup_components(detections)
data_geracao = datetime.now().strftime("%d/%m/%Y %H:%M")
linhas = []
linhas.append(f"# Relatório de Modelagem de Ameaças (STRIDE)")
linhas.append(f"**Sistema:** {sistema_nome} ")
linhas.append(f"**Data de geração:** {data_geracao} ")
linhas.append(f"**Gerado por:** pipeline de detecção de componentes + base de conhecimento STRIDE ")
if demo_mode:
linhas.append("\n> ⚠️ **Modo demonstração**: nenhum modelo treinado foi encontrado em "
"`models/best.pt`. Os componentes abaixo foram estimados por uma "
"heurística de cor/contorno apenas para fins de demonstração da interface. "
"Execute `train.py` para obter detecções reais do modelo supervisionado.\n")
linhas.append("\n---\n")
if not counts:
linhas.append("Nenhum componente foi identificado no diagrama enviado. "
"Verifique a qualidade/resolução da imagem e tente novamente.")
return "\n".join(linhas)
linhas.append("## 1. Componentes Identificados\n")
linhas.append("| Componente | Ocorrências |")
linhas.append("|---|---|")
for label, qtd in sorted(counts.items(), key=lambda x: -x[1]):
linhas.append(f"| {label.replace('_', ' ').title()} | {qtd} |")
linhas.append("\n## 2. Ameaças por Componente (STRIDE)\n")
total_ameacas = 0
for label in counts:
threats = get_threats_for_component(label)
if not threats:
continue
linhas.append(f"### {label.replace('_', ' ').title()}\n")
threats_ordenadas = sorted(threats, key=lambda t: SEVERIDADE_ORDEM.get(t.severidade, 9))
for t in threats_ordenadas:
total_ameacas += 1
linhas.append(f"**[{t.categoria}] {t.nome_categoria} — Severidade: {t.severidade}**")
linhas.append(f"- *Ameaça:* {t.descricao}")
linhas.append(f"- *Contramedida recomendada:* {t.contramedida}\n")
linhas.append("## 3. Resumo Executivo\n")
n_criticas = sum(1 for label in counts for t in get_threats_for_component(label) if t.severidade == "Crítica")
n_altas = sum(1 for label in counts for t in get_threats_for_component(label) if t.severidade == "Alta")
linhas.append(f"- Componentes identificados: **{len(counts)}**")
linhas.append(f"- Ameaças mapeadas: **{total_ameacas}**")
linhas.append(f"- Ameaças de severidade **Crítica**: **{n_criticas}**")
linhas.append(f"- Ameaças de severidade **Alta**: **{n_altas}**")
linhas.append("\n> Este relatório é gerado automaticamente por um MVP do fluxo e deve ser "
"revisado por um especialista em segurança antes de decisões de mitigação.")
return "\n".join(linhas)
def save_pdf_report(markdown_text: str, output_path: str = "relatorio_stride.pdf"):
"""Converte o relatório (texto simples, sem renderização rica de markdown) para PDF."""
from fpdf import FPDF
pdf = FPDF()
pdf.set_auto_page_break(auto=True, margin=15)
pdf.add_page()
pdf.set_font("Helvetica", size=11)
for raw_line in markdown_text.split("\n"):
line = raw_line.replace("**", "").replace("### ", "").replace("## ", "").replace("# ", "")
line = line.replace("*Ameaça:*", "Ameaca:").replace("*Contramedida recomendada:*", "Contramedida:")
# A fonte core (Helvetica) do fpdf2 só suporta latin-1; removemos
# caracteres fora dessa faixa (ex.: emojis) para evitar falhas de render.
line = line.encode("latin-1", errors="ignore").decode("latin-1")
if line.strip() == "":
pdf.ln(4)
continue
pdf.set_x(pdf.l_margin)
pdf.multi_cell(0, 6, line)
pdf.output(output_path)
return output_path