Add false-positive triage via package-ownership provenance

A noisy IDS gets ignored. Adds explicit FP-handling built on provenance — a
binary owned by the package manager is almost certainly legitimate (the OSSEC
rootcheck / AIDE principle).

- provenance.py: package_owner()/is_package_owned() via pacman/dpkg/rpm, cached;
  framed as confidence-raising, never proof-of-safety
- triage.py: triage_alert() labels each detection likely-FP vs review with a
  reason (package-owned binary, loopback-only listener, allowlisted comm, …);
  reverse_shell/egress/exec rules are ALWAYS review (provenance can't clear a
  network shell); unattributable listeners are reviewed, not cleared
- cli: `enodia-sentinel triage` summarizes captured alerts and suggests
  allowlist entries
- new_listener: listener_allow_comms + optional suppress_package_owned_listeners
  gate (the best single knob for a desktop/seedbox running P2P apps)
- tests: +10 (provenance injected); README + config documented. 65/65 pass

Verified on a live seedbox: 12 detections, 11 auto-cleared as FP (qbittorrent /
nicotine / kdeconnectd / the dashboard itself), 1 correctly held for review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Luna 2026-05-31 22:05:00 -07:00
parent c00fff224c
commit 07f5261d59
8 changed files with 332 additions and 0 deletions

View file

@ -236,6 +236,43 @@ notify_ntfy_url = "https://ntfy.sh"
notify_ntfy_topic = "enodia-7Hq2x" # keep this secret — it's the access control
```
## False positives & triage
A host IDS that cries wolf gets ignored, so Sentinel ships explicit tooling to
separate benign noise from real findings — built on **provenance**: a binary
shipped by your package manager (`pacman`/`dpkg`/`rpm`) is overwhelmingly likely
to be legitimate (the same idea behind OSSEC's rootcheck and AIDE).
```bash
enodia-sentinel triage # classify captured alerts, suggest allowlist entries
```
```
12 distinct detections — 11 likely false-positive, 1 to review.
[FP ] new_listener x99 listener binary is package-owned (qbittorrent)
[FP ] new_listener x1 loopback-only listener (not externally reachable)
[REVIEW] new_listener x1 unrecognized listener *:1740 (?)
...
To suppress the false positives, add to your config:
# listener_allow_comms += "qbittorrent"
```
Triage is deliberately conservative: `reverse_shell`, `egress`, and the eBPF
exec rules are **always** flagged review (provenance can't clear a network
shell), and any listener it *can't* attribute to a process is reviewed rather
than cleared. Suppression is never automatic — you choose what to allowlist.
Knobs to quiet known-good activity:
| Config | Effect |
|---|---|
| `listener_allow_comms` | never alert on listeners owned by these apps |
| `listener_allow_ports` | never alert on these ports |
| `suppress_package_owned_listeners` | drop `new_listener` when the binary is package-owned (best single knob for a desktop/seedbox) |
| `egress_allow_cidrs` | trusted public ranges for the egress detector |
| `suid_hot_dirs` / `exec_rules_file` | tune SUID criticality / add custom exec rules |
## Security model
Sentinel runs as root because it must read every process's `/proc`, the full