Skip to content

Baseline monitor

Source: periodic snapshots of system state (cadence is a tunable default — see the Hardening checklist).

The baseline monitor catches drift — changes to the system surface area that didn't go through your normal change-management. It's the closest thing Fenrir has to a tripwire.

What it watches

Four categories, snapshotted every cycle and compared against the previous saved baseline:

Category What's captured Snapshot mechanism
Listening ports port + protocol + owning process ss -tlnpH and ss -ulnpH
Enabled systemd services service name + state systemctl list-unit-files --state=enabled
Users name, UID, shell, home (UID 0 or ≥ 1000) parse /etc/passwd
Setuid binaries path find /usr /bin /sbin /opt -xdev -perm -4000 -type f

The baseline is persisted as JSON in data/baseline.json (mode 0640, owned by the service user). On the first run after install, the baseline is just established — no events. From the second run onwards, every added or removed item produces an event.

Event types and severity

Event type What it means Default severity
baseline_new_ports A port started listening that wasn't there before HIGH
baseline_removed_ports A port stopped listening LOW
baseline_new_services A new systemd service was enabled HIGH
baseline_removed_services A service was disabled MEDIUM
baseline_new_users A new user account appeared CRITICAL
baseline_removed_users An existing user disappeared HIGH
baseline_new_setuid A new setuid binary appeared CRITICAL
baseline_removed_setuid A setuid bit was removed LOW

The two CRITICAL classes — new user, new setuid — are deliberately the loudest. Both are textbook persistence techniques. Even a legitimate apt install that creates a system user (e.g. ollama user) will fire the alert; the AI investigation will then check if it correlates with a recent package_install event in the same time window and likely close it as false_positive with confidence ≥ 80.

Sample events

A new user appearing:

{
  "source": "baseline",
  "category": "baseline_new_users",
  "severity": "CRITICAL",
  "description": "Nuovo utente di sistema: alice (uid=1001, shell=/bin/bash)",
  "raw_line": "baseline new users: alice ({'uid': 1001, 'shell': '/bin/bash', 'home': '/home/alice'})"
}

A new setuid binary:

{
  "source": "baseline",
  "category": "baseline_new_setuid",
  "severity": "CRITICAL",
  "description": "Nuovo binario SETUID: /usr/local/bin/myutil",
  "raw_line": "baseline new setuid: /usr/local/bin/myutil"
}

Tuning the cadence

The default ships balanced for responsiveness vs CPU cost. Override via the BaselineMonitor constructor in app.py if you want a different cadence — but the setuid scan does a find over /usr /bin /sbin /opt which on a busy server takes seconds, so very short intervals are not recommended. Production-grade values: see the Hardening checklist.

When you legitimately make changes

If you intentionally create a new user, install a new service, or open a new port, just acknowledge the alert in the dashboard or Telegram. The investigation will still get persisted (good for the audit trail), and you have evidence that the change went through your approved process.

For frequent legitimate changes (CI auto-deploys, automated certbot renewals), consider:

  1. Re-baselining after a change: delete data/baseline.json (or specific keys) and let the next run reseed.
  2. Future: a BASELINE_IGNORE_PATTERNS config that lets you whitelist /etc/letsencrypt/, tmpfs, etc.

What this catches that nothing else does

The baseline monitor is the only way Fenrir catches:

  • Backdoor users — the classic post-compromise persistence
  • Reverse-shell daemons opening a listening port
  • Modified setuid binaries introduced via a compromised package or sloppy install
  • Hidden services that bypass your IaC and weren't in your runbook

It's worth its weight even if you never use any other Fenrir feature.