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:
Luna 2026-06-10 17:52:42 -07:00
parent 4015ec872b
commit a56d72edd6
9 changed files with 545 additions and 13 deletions

View file

@ -155,6 +155,34 @@ Exit code:
- `0`: sampled files match and signature policy is not downgraded.
- `1`: a signed-package mismatch or insecure signature setting was found.
### `incident`
```bash
enodia-sentinel incident list [--json]
enodia-sentinel incident show <incident-id> [--json]
enodia-sentinel incident export <incident-id>
```
Groups related alerts into incidents so one intrusion reads as one story instead
of several disconnected alerts. Grouping is **process-lineage first** (alerts
sharing a PID or a common ancestor — the web server or SSH session they descend
from), with a **time-window fallback** for alerts that carry no live PID (FIM
drift, package tamper, hidden modules). Each captured snapshot records an
`incident_id`; the index lives in `incidents.json` under `log_dir`.
- `list` — incidents newest-first: id, last seen, severity, alert count, and the
signatures involved.
- `show <id>` — incident summary plus a time-ordered timeline of its member
snapshots (time, severity, signatures, PIDs, and the snapshot file to open).
- `export <id>` — a single JSON bundle of the incident record with every member
snapshot report inlined, suitable for handoff or offline analysis.
Tuning lives in config: `incident_window` (how long an incident stays open for
new alerts) and `incident_lineage_depth` (ancestors walked when correlating).
Exit code: `0` on success; `1` if the incident id is unknown; `2` if `show`/
`export` is called without an id.
### `posture check`
```bash
@ -255,9 +283,6 @@ does not require pip or a virtualenv.
The roadmap reserves these command shapes:
```bash
enodia-sentinel incident list
enodia-sentinel incident show <incident-id>
enodia-sentinel incident export <incident-id>
enodia-sentinel respond plan <incident-id>
enodia-sentinel respond apply <plan-id>
```