"""Plain-text report renderer.""" from __future__ import annotations from ..engine import AnalysisResult from ..findings import Severity from ._common import ( SEVERITY_LABEL, affected_line, evidence_line, artifact_line, sources_list, inventory_lines, ) def render(result: AnalysisResult, extra: dict | None = None) -> str: m = result.meta s = result.summary out: list[str] = [] line = "=" * 70 out.append(line) out.append("IoT PreTester 4.6 Demo - Network Capture Security Analysis") out.append(line) out.append(f"Capture : {m.get('capture_path', '-')}") out.append(f"Packets : {m.get('packet_count', 0)} Duration: {m.get('duration_seconds', 0)}s") out.append(f"Findings: {m.get('finding_count', 0)}") if m.get("device_ip"): out.append(f"Device : {m['device_ip']} (device-related findings: {s.get('device_related', 0)}, " f"other hosts: {s.get('other_hosts', 0)})") if m.get("truncated"): out.append(f"WARNING : partial analysis - {m.get('truncation_reason')}") out.append("") bysev = s.get("by_severity", {}) out.append("SUMMARY") out.append("-" * 70) for sev in Severity: out.append(f" {SEVERITY_LABEL[sev]:<8}: {bysev.get(sev.value, 0)}") out.append("") highlights = s.get("highlights", []) if highlights: out.append("PRIORITIZED HIGHLIGHTS") out.append("-" * 70) for f in highlights: out.append(f" [{SEVERITY_LABEL[f.severity]}/{f.confidence.value}] " f"{f.title} -- {affected_line(f)}") out.append("") out.append("FINDINGS") out.append("-" * 70) if not result.findings: out.append(" No security-relevant findings detected.") for i, f in enumerate(result.findings, 1): out.append(f"[{i}] {f.title}") out.append(f" Severity : {SEVERITY_LABEL[f.severity]} / Confidence: {f.confidence.value}") out.append(f" Category : {f.category.value} (Detector: {f.detector})") art = artifact_line(f) if art and art != "—": out.append(f" ARTIFACT : {art}") srcs = sources_list(f) if srcs: out.append(" Sources :") for source in srcs: out.append(f" - {source}") out.append(f" Risk : {f.risk_description}") out.append(f" Affected : {affected_line(f)}") out.append(f" Evidence : {evidence_line(f)}") out.append(f" Recommendation : {f.recommendation}") out.append(f" Finding ID : {f.finding_id}") out.append("") inv_lines = inventory_lines(result.inventory, md=False) if inv_lines: out.append("-" * 70) out.extend(inv_lines) out.append("") if extra: hashes = extra.get("hashes") or [] urls = extra.get("urls") or [] if hashes: out.append(f"HASHES ({len(hashes)})") out.append("-" * 70) for h in hashes: if h.get("cracked"): state = f"CRACKED -> {h.get('cracked_value')}" if h.get("crack_time"): state += f" ({h['crack_time']})" elif h.get("analyzed"): state = "not cracked" else: state = "analysis pending" htype = ", ".join(h.get("hash_type") or []) out.append(f" {h.get('hash_value')}{f' [{htype}]' if htype else ''}") out.append(f" {state}") out.append("") if urls: out.append(f"CLOUD URLS & ENDPOINTS ({len(urls)})") out.append("-" * 70) for u in urls: out.append(f" {u.get('url')}") if u.get("analyzed"): ports = ", ".join(map(str, u.get("ports") or [])) or "-" out.append(f" Ports: {ports}") if u.get("tls_versions"): out.append(f" TLS: {', '.join(u['tls_versions'])}") if u.get("cipher_suites"): out.append(f" Cipher: {', '.join(u['cipher_suites'])}") else: out.append(" analysis pending") out.append("") return "\n".join(out)