Add incident grouping (roadmap v0.8: incident list/show/export)
Collapse related alerts into one incident, process-lineage first with a time-window fallback. Each alert batch's flagged PIDs are walked up the /proc PPid chain into a lineage set (excluding pid 0/1 so everything doesn't correlate through init); batches whose lineage sets intersect — sharing a process or a common ancestor like the web server or SSH session — join the same incident. PID-less batches (FIM drift, package tamper, hidden modules) fall back to the most recently active open incident within incident_window. snapshot.capture now computes lineage and records each snapshot into a JSON incident index (incidents.json), writing incident_id into both the report JSON (report level, so the per-alert schema is untouched) and the text header. New commands: incident list incidents newest-first, with signatures incident show <id> summary + time-ordered snapshot timeline incident export <id> JSON bundle: record + inlined snapshots The lineage/assign cores are pure functions; record() serializes the index under a lock (capture runs from sweep + eBPF threads) and is best-effort so an index problem never loses a snapshot. 17 new tests (lineage, assign, record, and an end-to-end capture→group→CLI path). Config: incident_tracking / incident_window / incident_lineage_depth. Docs + sample config updated; closes the last v0.8 roadmap item. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
4015ec872b
commit
a56d72edd6
9 changed files with 545 additions and 13 deletions
|
|
@ -116,6 +116,8 @@ def _format_text(report: dict, extras: dict[str, str]) -> str:
|
|||
L.append(f"Time: {report['time']}")
|
||||
L.append(f"Host: {report['host']}")
|
||||
L.append(f"Severity: {report['severity']}")
|
||||
if report.get("incident_id"):
|
||||
L.append(f"Incident: {report['incident_id']}")
|
||||
L.append("")
|
||||
L.append("## Triggering detections")
|
||||
for a in report["alerts"]:
|
||||
|
|
@ -161,11 +163,20 @@ def capture(alerts: list[Alert], state: SystemState, cfg: Config) -> Path:
|
|||
|
||||
severity = max((a.severity for a in alerts), default=Severity.HIGH)
|
||||
pids: list[int] = sorted({p for a in alerts for p in a.pids})
|
||||
host = os.uname().nodename
|
||||
|
||||
# Group this batch into an incident by process lineage (then time). Additive:
|
||||
# the per-alert JSON schema is unchanged; incident_id sits at report level.
|
||||
from . import incident
|
||||
lineage = incident.lineage_from_state(pids, state, cfg)
|
||||
incident_id = incident.record(
|
||||
cfg, base.with_suffix(".log").name, alerts, lineage, now.timestamp(), host)
|
||||
|
||||
report = {
|
||||
"time": now.isoformat(),
|
||||
"host": os.uname().nodename,
|
||||
"host": host,
|
||||
"severity": str(severity),
|
||||
"incident_id": incident_id,
|
||||
"alerts": [a.to_dict() for a in alerts],
|
||||
"processes": [_pid_detail(state, p) for p in pids],
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue