build 4.6 Demo

This commit is contained in:
DaStra3003
2026-08-19 08:04:59 +02:00
commit ff8f1ca989
123 changed files with 17696 additions and 0 deletions
@@ -0,0 +1,25 @@
"""Report renderers for Markdown, plain text, and JSON."""
from __future__ import annotations
from ..engine import AnalysisResult
from . import markdown as _md
from . import text as _txt
from . import json_report as _json
FORMATS = ("md", "txt", "json")
def render(result: AnalysisResult, fmt: str, extra: dict | None = None) -> str:
"""Render a report with optional endpoint and hash enrichment sections."""
fmt = (fmt or "md").lower()
if fmt in ("md", "markdown"):
return _md.render(result, extra)
if fmt in ("txt", "text"):
return _txt.render(result, extra)
if fmt == "json":
return _json.render(result, extra)
raise ValueError(f"Unknown report format: {fmt!r} (allowed: {', '.join(FORMATS)})")
__all__ = ["render", "FORMATS"]
@@ -0,0 +1,107 @@
"""Shared report-rendering helpers."""
from __future__ import annotations
import datetime as _dt
from ..findings import Finding, Severity
SEVERITY_LABEL = {
Severity.CRITICAL: "CRITICAL",
Severity.HIGH: "HIGH",
Severity.MEDIUM: "MEDIUM",
Severity.LOW: "LOW",
Severity.INFO: "INFO",
}
def fmt_ts(ts) -> str:
if ts is None:
return "—"
try:
return _dt.datetime.fromtimestamp(float(ts), _dt.timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
except Exception:
return str(ts)
def affected_line(f: Finding) -> str:
"""Format affected devices, endpoints, and ports as one compact line."""
parts = []
if f.affected_device:
parts.append(f"Device {f.affected_device}")
if f.source_ip:
parts.append(f"Source {f.source_ip}")
if f.destination_ip:
parts.append(f"Destination {f.destination_ip}")
if f.protocol:
proto = f.protocol + (f":{f.port}" if f.port else "")
parts.append(proto)
for k in ("host", "registered_domain", "query_name", "target"):
if f.evidence.get(k):
parts.append(str(f.evidence[k]))
break
return ", ".join(parts) if parts else "—"
# Evidence rendered in dedicated sections is excluded from generic details.
_EVIDENCE_SKIP = {
"dedup_key", "sources", "artifact", "capture_file", "source_packet_count",
"occurrences",
}
def artifact_line(f: Finding) -> str:
"""Format the primary artifact as one compact line."""
art = f.evidence.get("artifact")
if isinstance(art, dict) and art.get("value"):
label = art.get("label")
return f"{label}: {art['value']}" if label else str(art["value"])
if art:
return str(art)
return "—"
def fmt_source(ref: dict) -> str:
"""Format one packet-level source reference."""
parts = []
if ref.get("packet") is not None:
parts.append(f"Packet #{ref['packet']}")
if ref.get("stream") is not None:
parts.append(f"Stream {ref['stream']}")
if ref.get("timestamp") is not None:
parts.append(fmt_ts(ref["timestamp"]))
if ref.get("src_ip") or ref.get("dst_ip"):
parts.append(f"{ref.get('src_ip', '?')} → {ref.get('dst_ip', '?')}")
if ref.get("protocol"):
proto = ref["protocol"] + (f":{ref['port']}" if ref.get("port") else "")
parts.append(proto)
if ref.get("capture"):
parts.append(f"Capture {ref['capture']}")
if ref.get("note"):
parts.append(str(ref["note"]))
return " · ".join(parts) if parts else "—"
def sources_list(f: Finding) -> list[str]:
"""Return formatted packet-level source references for a finding."""
refs = f.evidence.get("sources") or []
lines = [fmt_source(r) for r in refs]
total = f.evidence.get("source_packet_count")
if total and len(f.related_packets or []) and total > len(refs):
lines.append(f"… and {total - len(refs)} more packets")
return lines
def evidence_line(f: Finding) -> str:
"""Format packet, stream, time, and detector-specific evidence."""
parts = []
if f.related_packets:
pk = ", ".join(map(str, f.related_packets[:8]))
parts.append(f"Packets: {pk}")
if f.related_streams:
parts.append(f"Streams: {', '.join(map(str, f.related_streams[:8]))}")
parts.append(f"Time: {fmt_ts(f.timestamp_first_seen)} – {fmt_ts(f.timestamp_last_seen)}")
extras = {k: v for k, v in f.evidence.items() if k not in _EVIDENCE_SKIP and v is not None}
if extras:
kv = ", ".join(f"{k}={v}" for k, v in list(extras.items())[:6])
parts.append(kv)
return " | ".join(parts)
@@ -0,0 +1,14 @@
"""Machine-readable JSON report renderer."""
from __future__ import annotations
import json
from ..engine import AnalysisResult
def render(result: AnalysisResult, extra: dict | None = None) -> str:
data = result.to_dict()
if extra:
data["cloud_results"] = extra
return json.dumps(data, indent=2, ensure_ascii=False, default=str)
@@ -0,0 +1,111 @@
"""Markdown report renderer."""
from __future__ import annotations
from ..engine import AnalysisResult
from ..findings import Severity
from ._common import (
SEVERITY_LABEL, fmt_ts, affected_line, evidence_line, artifact_line, sources_list,
)
def render(result: AnalysisResult, extra: dict | None = None) -> str:
m = result.meta
s = result.summary
out: list[str] = []
out.append("# IoT PreTester 4.6 Demo — Network Capture Security Analysis")
out.append("")
out.append(f"- **Capture:** `{m.get('capture_path', '—')}`")
out.append(f"- **Packets:** {m.get('packet_count', 0)} · "
f"**Duration:** {m.get('duration_seconds', 0)} s · "
f"**Findings:** {m.get('finding_count', 0)}")
out.append("")
bysev = s.get("by_severity", {})
out.append("## Summary")
out.append("")
out.append("| Severity | Count |")
out.append("| ----------- | ------ |")
for sev in Severity:
out.append(f"| {SEVERITY_LABEL[sev]} | {bysev.get(sev.value, 0)} |")
out.append("")
highlights = s.get("highlights", [])
if highlights:
out.append("## ⚠️ Prioritized Highlights")
out.append("")
out.append("Highest-impact credential, token, C2, and exfiltration findings:")
out.append("")
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("")
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("")
out.append(f"- **Severity:** {SEVERITY_LABEL[f.severity]} "
f"· **Confidence:** {f.confidence.value} "
f"· **Category:** {f.category.value} "
f"· **Detector:** {f.detector}")
art = artifact_line(f)
if art and art != "—":
out.append(f"- **🔎 Observed 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"- **Additional evidence:** {evidence_line(f)}")
out.append(f"- **Recommendation:** {f.recommendation}")
out.append(f"- **Finding ID:** `{f.finding_id}`")
out.append("")
# Optional URL and hash enrichment results.
if extra:
hashes = extra.get("hashes") or []
urls = extra.get("urls") or []
if hashes:
out.append("")
out.append(f"## Hashes ({len(hashes)})")
out.append("")
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"{f' [{htype}]' if htype else ''} — {state}")
if urls:
out.append("")
out.append(f"## Cloud URLs and Endpoints ({len(urls)})")
out.append("")
for u in urls:
if u.get("analyzed"):
ports = ", ".join(map(str, u.get("ports") or [])) or "—"
details = f"Ports: {ports}"
if u.get("tls_versions"):
details += f"; TLS: {', '.join(u['tls_versions'])}"
if u.get("cipher_suites"):
details += f"; Cipher: {', '.join(u['cipher_suites'])}"
else:
details = "analysis pending"
out.append(f"- `{u.get('url')}` — {details}")
out.append("")
out.append("---")
out.append(f"_Generated by IoT PreTester 4.6 Demo · Detectors: "
f"{', '.join(m.get('detectors_run', []))}_")
return "\n".join(out)
+100
View File
@@ -0,0 +1,100 @@
"""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,
)
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)}")
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("")
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)