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

@ -31,6 +31,14 @@ egress_allow_cidrs = []
# Listener ports that never alert even if they appear after baseline.
listener_allow_ports = []
# Listeners owned by these process names never alert (e.g. P2P clients that
# churn ports: qbittorrent, transmission, nicotine, kdeconnectd, syncthing…).
listener_allow_comms = []
# Suppress new_listener when the listening binary ships with an installed
# package (strong provenance signal). The single best knob for a desktop /
# seedbox that runs lots of legitimate networked apps. Off by default so the
# detector stays honest; turn on if new_listener is noisy.
suppress_package_owned_listeners = false
# Dirs where any SUID binary is treated as CRITICAL (attacker-writable).
suid_hot_dirs = ["/tmp", "/dev/shm", "/var/tmp", "/home", "/run/user"]