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:
- Re-baselining after a change: delete
data/baseline.json(or specific keys) and let the next run reseed. - Future: a
BASELINE_IGNORE_PATTERNSconfig 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.