build 4.6 Demo
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"""Run all registered detectors and return a prioritized analysis result."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from .context import CaptureContext
|
||||
from .findings import Finding
|
||||
from . import scoring
|
||||
from . import provenance
|
||||
from . import detectors as detectors_pkg
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnalysisResult:
|
||||
findings: list[Finding] = field(default_factory=list)
|
||||
summary: dict = field(default_factory=dict)
|
||||
meta: dict = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"meta": self.meta,
|
||||
"summary": _summary_to_dict(self.summary),
|
||||
"findings": [f.to_dict() for f in self.findings],
|
||||
}
|
||||
|
||||
|
||||
class AnalysisEngine:
|
||||
def __init__(self, detector_list=None):
|
||||
# Use every registered detector unless a test supplies a subset.
|
||||
self.detectors = detector_list if detector_list is not None else detectors_pkg.build_all()
|
||||
|
||||
def analyze_context(self, ctx: CaptureContext) -> AnalysisResult:
|
||||
started = time.time()
|
||||
raw: list[Finding] = []
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
for det in self.detectors:
|
||||
try:
|
||||
raw.extend(list(det.detect(ctx)))
|
||||
except Exception as exc: # One detector must not abort the full analysis.
|
||||
errors[det.name] = repr(exc)
|
||||
|
||||
deduped = scoring.dedupe(raw)
|
||||
ordered = scoring.sort_by_priority(deduped)
|
||||
# Attach packet-level provenance before the summary is generated.
|
||||
capture_file = _basename(ctx.path)
|
||||
for f in ordered:
|
||||
_enrich_provenance(f, capture_file)
|
||||
summary = scoring.summarize(ordered)
|
||||
|
||||
return AnalysisResult(
|
||||
findings=ordered,
|
||||
summary=summary,
|
||||
meta={
|
||||
"capture_path": ctx.path,
|
||||
"packet_count": ctx.packet_count,
|
||||
"duration_seconds": round(ctx.duration, 3),
|
||||
"detectors_run": [d.name for d in self.detectors],
|
||||
"detector_errors": errors,
|
||||
"analysis_time_seconds": round(time.time() - started, 3),
|
||||
"raw_finding_count": len(raw),
|
||||
"finding_count": len(ordered),
|
||||
},
|
||||
)
|
||||
|
||||
def analyze_pcap(self, path: str, packet_limit: Optional[int] = None) -> AnalysisResult:
|
||||
# Keep context-only analysis usable without pyshark or tshark.
|
||||
from .extract_pyshark import build_context_from_pcap
|
||||
ctx = build_context_from_pcap(path, packet_limit=packet_limit)
|
||||
return self.analyze_context(ctx)
|
||||
|
||||
|
||||
def result_from_dict(data: dict) -> AnalysisResult:
|
||||
"""Reconstruct a result and recalculate its summary from stored findings."""
|
||||
findings = [Finding.from_dict(f) for f in data.get("findings", [])]
|
||||
summary = scoring.summarize(findings)
|
||||
meta = data.get("meta", {})
|
||||
return AnalysisResult(findings=findings, summary=summary, meta=meta)
|
||||
|
||||
|
||||
def _summary_to_dict(summary: dict) -> dict:
|
||||
out = dict(summary)
|
||||
if "highlights" in out:
|
||||
out["highlights"] = [f.to_dict() for f in out["highlights"]]
|
||||
if "top" in out:
|
||||
out["top"] = [f.to_dict() for f in out["top"]]
|
||||
return out
|
||||
|
||||
|
||||
def _basename(path) -> Optional[str]:
|
||||
if not path:
|
||||
return None
|
||||
import os
|
||||
return os.path.basename(str(path)) or str(path)
|
||||
|
||||
|
||||
def _enrich_provenance(f: Finding, capture_file) -> None:
|
||||
"""Add capture, artifact, and packet sources without replacing detector data."""
|
||||
ev = f.evidence
|
||||
if capture_file and not ev.get("capture_file"):
|
||||
ev["capture_file"] = capture_file
|
||||
if "artifact" not in ev:
|
||||
art = provenance.derive_artifact(ev)
|
||||
if art is not None:
|
||||
ev["artifact"] = art
|
||||
if "sources" not in ev:
|
||||
ev["sources"] = provenance.build_sources(
|
||||
related_packets=f.related_packets,
|
||||
related_streams=f.related_streams,
|
||||
src_ip=f.source_ip, dst_ip=f.destination_ip,
|
||||
protocol=f.protocol, port=f.port,
|
||||
timestamp_first_seen=f.timestamp_first_seen,
|
||||
capture=capture_file,
|
||||
)
|
||||
# Preserve the complete evidence count when the displayed source list is bounded.
|
||||
if f.related_packets and "source_packet_count" not in ev:
|
||||
ev["source_packet_count"] = len(f.related_packets)
|
||||
Reference in New Issue
Block a user