A host intrusion-detection daemon for Linux. Think of it as a watchdog for security and host stability purposes. https://enodiainformatics.com
Find a file
Luna 45f8acb24a Enodia Sentinel v0.1 — bash host-IDS prototype
Poll-based host intrusion-detection daemon modeled on freeze-watcher's
sample→trigger→snapshot→classify loop, with security signatures instead
of performance ones:

- reverse_shell  (interpreter with a network socket on stdio)
- ld_preload     (/etc/ld.so.preload or LD_PRELOAD in writable dirs)
- deleted_exe    (process running from a deleted/memfd binary)
- new_listener   (listening port absent from startup baseline)
- new_suid       (new SUID/SGID binary; critical in writable dirs)
- persistence    (cron/systemd/authorized_keys/rc-file changes)
- suspicious_egress (interpreter holding an outbound public connection)

Each capture writes a forensic snapshot with per-signature incident-
response guidance. Includes a safe, self-cleaning red-team harness
(sentinel-redteam), hardened systemd unit, config, Makefile, and Arch
packaging.

Tested on Arch: detectors fire on drills, no false positives on a clean
sweep, sweep cost optimized from ~15s to ~1s via batched syscalls and a
gated filesystem-wide SUID scan.

This bash implementation is retained as the regression oracle for the
forthcoming Python rewrite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 00:53:35 -07:00
config Enodia Sentinel v0.1 — bash host-IDS prototype 2026-05-31 00:53:35 -07:00
packaging Enodia Sentinel v0.1 — bash host-IDS prototype 2026-05-31 00:53:35 -07:00
src Enodia Sentinel v0.1 — bash host-IDS prototype 2026-05-31 00:53:35 -07:00
systemd Enodia Sentinel v0.1 — bash host-IDS prototype 2026-05-31 00:53:35 -07:00
.gitignore Enodia Sentinel v0.1 — bash host-IDS prototype 2026-05-31 00:53:35 -07:00
LICENSE Enodia Sentinel v0.1 — bash host-IDS prototype 2026-05-31 00:53:35 -07:00
Makefile Enodia Sentinel v0.1 — bash host-IDS prototype 2026-05-31 00:53:35 -07:00
README.md Enodia Sentinel v0.1 — bash host-IDS prototype 2026-05-31 00:53:35 -07:00

Enodia Sentinel

A host intrusion-detection daemon for Linux. It continuously runs a set of detectors over live system state — processes, sockets, file descriptors, the SUID inventory, and sensitive files — and writes a detailed forensic snapshot with incident-response guidance the moment a known attack signature appears.

Think of it as the security counterpart to a performance watchdog: instead of "I/O pressure spiked, here's the kernel state," it's "a shell just wired itself to a socket — here's the process tree, the peer, and what to do about it."

Design lineage. Sentinel reuses the proven control loop of a performance anomaly-capture daemon (sample → threshold/trigger → rich snapshot → classify → notify/retain) and swaps the inputs and signatures from performance to security. The forensic-snapshot-on-trigger pattern is the same; the detectors and the response guidance are new.

Why these detectors

Every detector keys on a behavior that is cheap to observe and expensive for an attacker to avoid — the high-signal, low-false-positive heuristics that real EDRs are built on:

Signature What it catches Why it's hard to evade
reverse_shell A shell/interpreter with a socket on fd 0/1/2 Interactive shells get a pty, not a socket — a socket on stdio is almost always nc -e / bash -i >& /dev/tcp/...
ld_preload_global A non-empty /etc/ld.so.preload Injecting into every process is the whole point of a userland rootkit; the file has to exist
ld_preload_proc LD_PRELOAD pointing into /tmp, /dev/shm, … Hooking libc to hide files/creds needs the library somewhere writable
deleted_exe A process running from a deleted / memfd: binary Fileless malware deletes its dropper but the kernel still names the inode (deleted)
new_listener A listening port absent from the startup baseline Bind shells and backdoors have to listen somewhere
new_suid A new SUID/SGID binary (critical in writable dirs) A SUID /tmp binary is a textbook privesc persistence trick
persistence Changes to cron, systemd units, authorized_keys, rc files Persistence has to write somewhere that survives reboot
suspicious_egress An interpreter holding an outbound conn to a public IP C2 beacons and exfil need to phone home

The control loop

        ┌────────────────────────────────────────────┐
        │  every SAMPLE_INTERVAL seconds               │
        │                                              │
        │   run_detectors() ──► alert lines            │
        │        │              SEV  signature  detail │
        │        ▼                                      │
        │   cooldown dedup (per signature+key)          │
        │        │                                      │
        │        ▼ (fresh alerts only)                  │
        │   capture_snapshot()                          │
        │     • flagged-pid deep dive (/proc)           │
        │     • full process tree + sockets             │
        │     • ld.so.preload, recent file mods         │
        │     • logins, failed auth, kernel modules     │
        │     • per-signature response guidance         │
        │        │                                      │
        │        ▼                                      │
        │   events.log + alert-*.log + desktop notify   │
        └────────────────────────────────────────────┘

Output lives in /var/log/enodia-sentinel/:

  • events.log — one line per alert (timestamp, severity, signatures, file)
  • alert-YYYYMMDD-HHMMSS.log — the full forensic snapshot

Quick start

sudo make install
sudo make enable          # start + enable the systemd service

# prove it works — in one terminal:
sudo tail -f /var/log/enodia-sentinel/events.log
# in another:
sentinel-redteam          # safe, self-cleaning attack simulations

You'll watch the drills trip reverse_shell, ld_preload_proc, deleted_exe, new_listener, and new_suid in real time, each producing a snapshot with response guidance.

Without installing (try it in place)

sudo ./src/sentinel.sh --baseline   # establish listener/SUID baselines
sudo ./src/sentinel.sh --check      # run every detector once, print findings

The red-team harness

sentinel-redteam is the demo and the regression test in one. It simulates each threat with safe, clearly-labeled stand-ins (everything tagged enodia-drill, auto-cleaned on exit):

  • reverse_shell — a bash whose stdio is a TCP socket to a local listener (no traffic leaves the box)
  • ld_preload — a process with LD_PRELOAD=/tmp/... (an empty file; never actually loaded, and it does not touch the system-wide /etc/ld.so.preload)
  • deleted_exe — a copy of /bin/sleep run from /tmp, then deleted
  • new_listener — an ephemeral nc -l on loopback
  • new_suid — a chmod 4755 copy of /bin/true in /tmp
  • persistence — writes to a sandbox authorized_keys (only fires if you opt the sandbox into WATCH_PERSISTENCE; it refuses to touch your real dotfiles)
sentinel-redteam --list                 # list drills
sentinel-redteam reverse_shell new_suid # run specific ones
HOLD=30 sentinel-redteam                 # keep artifacts alive 30s

Configuration

Edit /etc/enodia-sentinel.conf, then sudo systemctl restart enodia-sentinel.service. Key options:

Variable Default Purpose
SAMPLE_INTERVAL 4 seconds between detector sweeps
COOLDOWN 60 min seconds before re-alerting the same signature
DET_* 1 per-detector on/off switches
INTERPRETERS bash sh … process names treated as shells for rsh/egress
EGRESS_ALLOW_CIDRS "" trusted public ranges that won't trip egress
LISTENER_ALLOW_PORTS "" ports that never trip new_listener
SUID_HOT_DIRS /tmp /dev/shm … dirs where SUID = CRITICAL
WATCH_PERSISTENCE cron/systemd/… files watched for tampering
CAPTURE_EXECVE_BPFTRACE 0 add a bpftrace execve trace to snapshots
MAX_SNAPSHOTS / MAX_SNAPSHOT_AGE_DAYS 300 / 60 retention
NOTIFY_USERS "" desktop notify-send targets

Tuning out false positives

  • A long-running daemon legitimately running a deleted exe after a package upgrade won't alert — deleted_exe only fires for /tmp, /dev/shm, /run, /var/tmp, or memfd: paths.
  • Your own admin shells over SSH won't trip reverse_shell — they're on a pty.
  • Browsers and chat apps that interpreters open to public IPs can trip suspicious_egress if you add them to INTERPRETERS; the default list is deliberately scripting-focused. Use EGRESS_ALLOW_CIDRS for known-good ranges.

Security model

Sentinel runs as root because it must read every process's /proc, the full socket table, and root-owned files like authorized_keys. The systemd unit constrains that power hard: ProtectSystem=strict with the log dir as the only writable path, ProtectHome=read-only, NoNewPrivileges, MemoryDenyWriteExecute, and a minimal capability set (CAP_SYS_PTRACE, CAP_DAC_READ_SEARCH). It only ever reads the system and writes to its own log directory.

Roadmap — from polling to eBPF

This v0 is poll-based: it sweeps /proc and ss every few seconds. That's robust, dependency-light, and catches anything that lingers — but it can miss sub-second processes, and a sweep is observable. The planned evolution:

  1. bpftrace tracepoints (now, optional)CAPTURE_EXECVE_BPFTRACE=1 adds a live execve trace to each snapshot. The gentle on-ramp to kernel tracing.
  2. Event-driven execve/connect detection — replace the polling gap with bpftrace probes on sys_enter_execve, security_bprm_check, and tcp_connect, streamed to the daemon so short-lived reverse shells can't slip between sweeps.
  3. A libbpf + CO-RE agent (Go or Rust userland) — the real EDR core: LSM hooks, ring-buffer event streaming, per-process lineage tracking, and tamper-resistance. This is the production-grade rewrite the polling daemon prototypes the detection logic for.

The polling daemon isn't throwaway — it's the oracle: every signature here is a test case the eBPF agent must reproduce, and sentinel-redteam is the shared regression suite for both.

Project status

v0.1 — working poll-based detection across 7 signatures, forensic snapshots, red-team harness, systemd packaging. Built and tested on Arch Linux.

License

MIT