enodia-sentinal/config/enodia-sentinel.toml
Luna 5d577b624f Add file integrity monitoring (Tripwire-style), auto-refreshed via pacman hook
Detects binary/config tampering by content hash — catching a malicious swap
even when mtime is preserved (the gap in mtime-based persistence checks). Two
engines split by file ownership:

- fim.py hash baseline: SHA-256 (+ mode/uid/gid/size) of security-critical
  files the package manager doesn't track (/usr/local, /etc configs, systemd
  units, SSH keys). The baseline refreshes ONLY via fim-update / the pacman
  hook, so a flagged change stays flagged until acknowledged (Tripwire
  semantics). Alerts: fim_modified 100017 / fim_added 100018 / fim_removed 100019.
- package verification: `pacman -Qkk` checks package-owned binaries against the
  distro's own signed checksums — no baseline to maintain, implicitly current
  because the package DB updates on every upgrade. fim_pkg_modified 100020.

Auto-update on system updates: a pacman PostTransaction hook runs
`enodia-sentinel fim-update` after every install/upgrade/remove, so legitimate
package changes never alert — no manual `tripwire --update`.

- daemon: backgrounded FIM scan + optional pkg-verify on slow cadences, feeding
  the normal alert/snapshot/push pipeline; baseline loaded/built at startup
- cli: fim-baseline / fim-update / fim-check [--packages]
- config: fim_enabled, fim_paths, fim_scan_interval, fim_pkg_verify[_interval]
- packaging: ship + install the pacman hook (Makefile + PKGBUILD)
- tests: +7 (hashing, diff incl. mtime-preserving tamper, pacman -Qkk parse).
  72/72 pass. Verified end-to-end: a content swap with preserved mtime is caught.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 22:18:42 -07:00

116 lines
5.7 KiB
TOML

# Enodia Sentinel configuration (TOML).
# Read at startup from /etc/enodia-sentinel.toml (or $ENODIA_CONFIG).
# Restart after editing: sudo systemctl restart enodia-sentinel.service
#
# Every key is optional; omitted keys keep their built-in default.
# --- timing --------------------------------------------------------------
sample_interval = 4.0 # seconds between detector sweeps
cooldown = 60 # min seconds before re-alerting the same signature
baseline_grace = 10 # seconds after start before new-listener/suid arm
suid_refresh = 3600 # seconds between SUID baseline refreshes
suid_scan_interval = 60 # seconds between (backgrounded) SUID filesystem scans
# --- detectors (omit one to disable it) ----------------------------------
detectors = [
"reverse_shell", "ld_preload", "deleted_exe",
"new_listener", "new_suid", "persistence", "egress",
]
# --- tuning --------------------------------------------------------------
# Process names treated as "interpreters" for reverse-shell / egress checks.
interpreters = [
"bash", "sh", "dash", "zsh", "ksh", "ash", "nc", "ncat", "netcat",
"socat", "telnet", "python", "python2", "python3", "perl", "ruby",
"php", "lua", "awk",
]
# Trusted public CIDRs that should NOT trip the egress detector
# (your VPS, VPN exit, monitoring endpoints, …).
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"]
# Writable dirs always scanned for SUID even if on a separate filesystem
# (a tmpfs /tmp would otherwise be skipped by the on-device walk).
suid_scan_extra_dirs = ["/tmp", "/dev/shm", "/var/tmp", "/run/user"]
# Persistence files/dirs watched for modification.
watch_persistence = [
"/etc/cron.d", "/etc/crontab", "/etc/cron.daily", "/etc/cron.hourly",
"/etc/cron.weekly", "/var/spool/cron", "/etc/systemd/system",
"/etc/ld.so.preload", "/etc/passwd", "/etc/sudoers", "/etc/sudoers.d",
"/root/.ssh/authorized_keys", "/root/.bashrc", "/root/.profile",
]
# --- file integrity monitoring (FIM) ------------------------------------
# SHA-256 baseline of security-critical files the package manager doesn't track
# (configs, /usr/local, systemd units, keys). A pacman PostTransaction hook runs
# `enodia-sentinel fim-update` after every upgrade, so package changes never
# alert. Package-owned binaries are instead verified against the distro's own
# checksums via `pacman -Qkk` (fim_pkg_verify).
fim_enabled = true
fim_scan_interval = 300 # seconds between hash scans
fim_paths = [] # [] = built-in critical set; or your own list
fim_pkg_verify = false # run `pacman -Qkk` (thorough but slow)
fim_pkg_verify_interval = 21600 # seconds between package verifications
# --- eBPF event layer ----------------------------------------------------
# Event-driven execve monitor: catches short-lived processes the poll loop
# misses, matched against the Snort-style rule engine. Requires python-bpfcc
# and root; degrades gracefully to polling otherwise.
ebpf_exec_monitor = true
# Optional path to a TOML file of extra [[exec_rules]] (sid/msg/severity/
# classtype + path_prefixes/exec_comm/parent_comm/argv_regex).
exec_rules_file = ""
# Add a 3s bpftrace execve trace to each snapshot (requires the bpftrace pkg).
capture_execve_bpftrace = false
# --- retention -----------------------------------------------------------
max_snapshots = 300 # auto-delete oldest beyond this count (0 = no cap)
max_snapshot_age_days = 60 # auto-delete older than this (0 = no age cap)
# --- notifications: desktop ---------------------------------------------
notify_users = [] # e.g. ["luna"] — desktop notify-send on alert
notify_urgency = "critical" # low / normal / critical
# --- notifications: phone push ------------------------------------------
# A backend turns on when its required keys are set. Only alerts at or above
# notify_min_severity are pushed.
notify_min_severity = "HIGH" # MEDIUM / HIGH / CRITICAL
# ntfy (open-source, self-hostable). Install the ntfy app and subscribe to the
# topic; keep the topic name secret — it is the only access control on ntfy.sh.
notify_ntfy_url = "" # e.g. "https://ntfy.sh" or your own server
notify_ntfy_topic = "" # e.g. "enodia-<random>"
notify_ntfy_token = "" # optional Bearer token (protected topics)
# Pushover
notify_pushover_token = "" # application token
notify_pushover_user = "" # user/group key
# Generic webhook — receives the alert JSON via POST (Discord/Slack/your own).
notify_webhook_url = ""
# Optional dashboard base URL embedded in pushes (e.g. your Tailscale URL).
dashboard_url = ""
# --- web dashboard (read-only) ------------------------------------------
web_bind = "" # "" = auto-detect Tailscale IP; or an explicit address
web_port = 8787
web_token = "" # bearer token; auto-generated + saved if empty on a
# non-loopback bind. Set explicitly to keep the unit
# fully read-only.