Add platform docs; reposition as host security platform
Add docs/ (SPECIFICATION, ROADMAP, OPERATIONS, COMMAND_REFERENCE, THREAT_MODEL) describing the current v0.7 agent and the phased path from local HIDS to a host security platform. Reframe README, package metadata, and CLI/module descriptions from "intrusion-detection daemon" to "Linux host security platform", and surface the v0.7 signed-package verify (pkgdb_pkgverify*) and anti-rootkit (rootcheck_*) knobs in the sample config. Also wrap the pacman.conf read in pkgdb.siglevel_alert in a context manager to avoid a leaked file handle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1de5e86fed
commit
6ff2087329
11 changed files with 930 additions and 26 deletions
59
README.md
59
README.md
|
|
@ -1,14 +1,41 @@
|
||||||
# Enodia Sentinel
|
# Enodia Sentinel
|
||||||
|
|
||||||
A host intrusion-detection daemon for Linux. It continuously runs a set of
|
A Linux host security platform: intrusion detection, file/package integrity,
|
||||||
detectors over live system state — processes, sockets, file descriptors, the
|
anti-rootkit cross-checks, tamper-evidence, forensic snapshots, push alerts, and
|
||||||
SUID inventory, and sensitive files — and writes a detailed forensic snapshot
|
a read-only operator console.
|
||||||
(text **and** JSON) with incident-response guidance the moment a known attack
|
|
||||||
signature appears.
|
The current daemon continuously runs detectors over live system state —
|
||||||
|
processes, sockets, file descriptors, the SUID inventory, sensitive files, the
|
||||||
|
package database, and rootkit-hiding artifacts — then writes a detailed forensic
|
||||||
|
snapshot (text **and** JSON) with incident-response guidance the moment a known
|
||||||
|
attack signature appears.
|
||||||
|
|
||||||
Think of it as the security counterpart to a performance watchdog: instead of
|
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
|
"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."*
|
to a socket — here's the process tree, the peer, what changed on disk, whether
|
||||||
|
the package database was tampered with, and what to do next."*
|
||||||
|
|
||||||
|
## Product direction
|
||||||
|
|
||||||
|
Enodia is becoming more than an IDS. The local detector is the first layer of a
|
||||||
|
broader host security platform:
|
||||||
|
|
||||||
|
| Function | Current capability | Direction |
|
||||||
|
|---|---|---|
|
||||||
|
| Detect | Poll detectors + eBPF exec rules | More event sources and correlation |
|
||||||
|
| Verify | FIM, package DB anchor, signed-package checks | External anchors and signed evidence |
|
||||||
|
| Investigate | Text/JSON snapshots, dashboard, triage | Incident timelines and evidence export |
|
||||||
|
| Respond | Human guidance today | Dry-run response plans and audited containment |
|
||||||
|
| Assure | Heartbeat, watchdog, self-integrity, rootcheck | Fleet health and attestation-ready anchors |
|
||||||
|
|
||||||
|
Project docs:
|
||||||
|
|
||||||
|
- [Specification](docs/SPECIFICATION.md) — product model, current scope, target
|
||||||
|
platform shape, data model, and acceptance criteria.
|
||||||
|
- [Roadmap](docs/ROADMAP.md) — phased work from local sensor to incident,
|
||||||
|
response, fleet, and assurance layers.
|
||||||
|
- [Operations guide](docs/OPERATIONS.md) — install checks, health checks, alert
|
||||||
|
workflow, baseline hygiene, and evidence export.
|
||||||
|
|
||||||
> **Two implementations, on purpose.** The project began as a bash prototype
|
> **Two implementations, on purpose.** The project began as a bash prototype
|
||||||
> (`src/sentinel.sh`, kept as the regression **oracle**) and was re-architected
|
> (`src/sentinel.sh`, kept as the regression **oracle**) and was re-architected
|
||||||
|
|
@ -131,9 +158,8 @@ sudo tail -f /var/log/enodia-sentinel/events.log
|
||||||
sentinel-redteam # safe, self-cleaning attack simulations
|
sentinel-redteam # safe, self-cleaning attack simulations
|
||||||
```
|
```
|
||||||
|
|
||||||
You'll watch the drills trip `reverse_shell`, `ld_preload`, `deleted_exe`,
|
You'll watch the drills trip the core poll signatures in real time, each
|
||||||
`new_listener`, and `new_suid` in real time, each producing a `.log` + `.json`
|
producing a `.log` + `.json` snapshot with response guidance.
|
||||||
snapshot with response guidance.
|
|
||||||
|
|
||||||
Output lives in `/var/log/enodia-sentinel/`:
|
Output lives in `/var/log/enodia-sentinel/`:
|
||||||
- `events.log` — one line per alert
|
- `events.log` — one line per alert
|
||||||
|
|
@ -170,7 +196,7 @@ drill only sets the env var on a throwaway process.
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make test # 25 unit tests, stdlib unittest, no deps
|
make test # stdlib unittest suite, no runtime deps
|
||||||
```
|
```
|
||||||
|
|
||||||
Detectors are pure functions over an injectable `SystemState`, so tests build
|
Detectors are pure functions over an injectable `SystemState`, so tests build
|
||||||
|
|
@ -442,16 +468,11 @@ conscious tradeoff documented in the file itself.
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
1. **bpftrace tracepoints (optional)** — `capture_execve_bpftrace = true` adds a
|
The short version: Enodia grows from local HIDS into a host security platform:
|
||||||
live `execve` trace to each snapshot.
|
incident grouping, posture checks, response planning, richer eBPF telemetry,
|
||||||
2. ✅ **Event-driven `execve` detection (done)** — a real eBPF probe via bcc,
|
fleet health, external anchors, and eventually attestation-ready assurance.
|
||||||
feeding a Snort-style rule engine, so a short-lived process can't slip
|
|
||||||
between sweeps.
|
|
||||||
3. **More event sources** — `tcp_connect` (event-driven egress) and
|
|
||||||
`security_bprm_check` (LSM) probes, plus per-process lineage tracking.
|
|
||||||
4. **A libbpf + CO-RE agent (Go or Rust)** — the production EDR core: ring-buffer
|
|
||||||
event streaming, tamper resistance, no runtime compiler.
|
|
||||||
|
|
||||||
|
See [docs/ROADMAP.md](docs/ROADMAP.md) for the release tracks and phased plan.
|
||||||
The polling daemon isn't throwaway — it's the **oracle**: every signature is a
|
The polling daemon isn't throwaway — it's the **oracle**: every signature is a
|
||||||
test case the event layer must reproduce, and `sentinel-redteam` is the shared
|
test case the event layer must reproduce, and `sentinel-redteam` is the shared
|
||||||
regression suite for both.
|
regression suite for both.
|
||||||
|
|
|
||||||
|
|
@ -74,10 +74,24 @@ fim_pkg_verify_interval = 21600 # seconds between package verifications
|
||||||
pkgdb_verify = true
|
pkgdb_verify = true
|
||||||
pkgdb_interval = 600 # seconds between DB integrity checks
|
pkgdb_interval = 600 # seconds between DB integrity checks
|
||||||
heartbeat_max_age = 120 # daemon heartbeat older than this = stale/silent
|
heartbeat_max_age = 120 # daemon heartbeat older than this = stale/silent
|
||||||
|
# Layer 2: compare on-disk files to hashes in cached signed packages. This
|
||||||
|
# survives a rewritten local package DB, but needs a populated package cache and
|
||||||
|
# costs untar+hash work, so it samples packages on a slow cadence.
|
||||||
|
pkgdb_pkgverify = false
|
||||||
|
pkgdb_pkgverify_interval = 21600 # seconds between signed-package passes
|
||||||
|
pkgdb_pkgverify_sample = 40 # packages verified per pass (rotates)
|
||||||
# Sentinel's own binaries/config/units/hook are always FIM-watched (self-
|
# Sentinel's own binaries/config/units/hook are always FIM-watched (self-
|
||||||
# integrity). For real teeth, make them immutable: chattr +i <path>
|
# integrity). For real teeth, make them immutable: chattr +i <path>
|
||||||
# and run an external `enodia-sentinel watchdog` against this host's dashboard.
|
# and run an external `enodia-sentinel watchdog` against this host's dashboard.
|
||||||
|
|
||||||
|
# --- anti-rootkit cross-view --------------------------------------------
|
||||||
|
# Ask the same question two ways and diff the answers: kill(0) vs /proc for
|
||||||
|
# hidden processes, /sys/module vs /proc/modules for hidden LKMs, /proc/net/tcp
|
||||||
|
# vs ss for hidden listeners, and interface flags for promiscuous sniffing.
|
||||||
|
rootcheck_enabled = true
|
||||||
|
rootcheck_interval = 300 # seconds between cross-view sweeps
|
||||||
|
rootcheck_pid_cap = 65536 # upper PID to brute-force via kill(0)
|
||||||
|
|
||||||
# --- eBPF event layer ----------------------------------------------------
|
# --- eBPF event layer ----------------------------------------------------
|
||||||
# Event-driven execve monitor: catches short-lived processes the poll loop
|
# Event-driven execve monitor: catches short-lived processes the poll loop
|
||||||
# misses, matched against the Snort-style rule engine. Requires python-bpfcc
|
# misses, matched against the Snort-style rule engine. Requires python-bpfcc
|
||||||
|
|
|
||||||
226
docs/COMMAND_REFERENCE.md
Normal file
226
docs/COMMAND_REFERENCE.md
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
# Enodia Sentinel Command Reference
|
||||||
|
|
||||||
|
This is the operator-facing command contract for the current local agent. Commands
|
||||||
|
are intentionally plain: they either inspect state, manage baselines, run the
|
||||||
|
daemon, or expose existing evidence.
|
||||||
|
|
||||||
|
Global options:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel --version
|
||||||
|
enodia-sentinel -c /path/to/config.toml <command>
|
||||||
|
```
|
||||||
|
|
||||||
|
If no command is provided, `run` is used.
|
||||||
|
|
||||||
|
## Service Commands
|
||||||
|
|
||||||
|
### `run`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel run
|
||||||
|
```
|
||||||
|
|
||||||
|
Starts the daemon loop. This is the command used by the systemd service. It
|
||||||
|
builds or loads baselines, starts optional event monitoring, writes heartbeats,
|
||||||
|
runs detector sweeps, captures snapshots, and sends notifications.
|
||||||
|
|
||||||
|
Expected use: systemd, not an interactive shell.
|
||||||
|
|
||||||
|
### `check`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel check
|
||||||
|
```
|
||||||
|
|
||||||
|
Runs the enabled detector set once and prints alerts. This forces the SUID scan
|
||||||
|
and arms baseline-gated detectors immediately, so it is useful for health checks
|
||||||
|
and debugging.
|
||||||
|
|
||||||
|
Exit code:
|
||||||
|
|
||||||
|
- `0`: no alerts.
|
||||||
|
- `0`: alerts may still print today; this command is currently human-oriented,
|
||||||
|
not a strict CI gate.
|
||||||
|
|
||||||
|
### `baseline`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel baseline
|
||||||
|
```
|
||||||
|
|
||||||
|
Rebuilds listener and SUID baselines under `log_dir`. Use after installing on a
|
||||||
|
known-good host or after intentionally adding persistent services or privileged
|
||||||
|
helpers.
|
||||||
|
|
||||||
|
Do not run this to silence unexplained findings.
|
||||||
|
|
||||||
|
## Detection and Integrity Commands
|
||||||
|
|
||||||
|
### `list-detectors`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel list-detectors
|
||||||
|
```
|
||||||
|
|
||||||
|
Prints the poll detectors and whether each is enabled by config.
|
||||||
|
|
||||||
|
### `rootcheck`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel rootcheck
|
||||||
|
```
|
||||||
|
|
||||||
|
Runs anti-rootkit cross-view checks once:
|
||||||
|
|
||||||
|
- PIDs alive via `kill(pid, 0)` but missing from `/proc`.
|
||||||
|
- Live modules in `/sys/module` but missing from `/proc/modules`.
|
||||||
|
- Listening TCP ports in `/proc/net/tcp*` but missing from `ss`.
|
||||||
|
- Network interfaces in promiscuous mode.
|
||||||
|
|
||||||
|
Exit code:
|
||||||
|
|
||||||
|
- `0`: no rootcheck findings.
|
||||||
|
- `1`: one or more rootcheck alerts.
|
||||||
|
|
||||||
|
### `fim-baseline`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel fim-baseline
|
||||||
|
```
|
||||||
|
|
||||||
|
Builds the SHA-256 file integrity baseline for configured critical paths plus
|
||||||
|
Sentinel's own footprint.
|
||||||
|
|
||||||
|
Use on a known-good host. The package hook also calls `fim-update` after normal
|
||||||
|
package transactions.
|
||||||
|
|
||||||
|
### `fim-update`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel fim-update
|
||||||
|
```
|
||||||
|
|
||||||
|
Refreshes the FIM baseline. This exists primarily for the package manager hook.
|
||||||
|
Manual use should be treated as an acknowledgement that the current file state
|
||||||
|
is legitimate.
|
||||||
|
|
||||||
|
### `fim-check`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel fim-check
|
||||||
|
enodia-sentinel fim-check --packages
|
||||||
|
```
|
||||||
|
|
||||||
|
Diffs current file state against the FIM baseline. With `--packages`, also runs
|
||||||
|
package-owned file verification through the package manager (`pacman -Qkk` on
|
||||||
|
Arch).
|
||||||
|
|
||||||
|
Exit code:
|
||||||
|
|
||||||
|
- `0`: no baseline changes.
|
||||||
|
- `1`: one or more added, removed, or modified files.
|
||||||
|
|
||||||
|
### `pkgdb-check`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel pkgdb-check
|
||||||
|
```
|
||||||
|
|
||||||
|
Checks whether the local package database fingerprint changed outside a logged
|
||||||
|
package transaction. This catches an attacker rewriting stored package checksums
|
||||||
|
to hide a modified binary.
|
||||||
|
|
||||||
|
Exit code:
|
||||||
|
|
||||||
|
- `0`: package DB matches the anchor or was legitimately re-anchored.
|
||||||
|
- `1`: out-of-band DB tampering suspected.
|
||||||
|
|
||||||
|
### `pkgdb-verify`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel pkgdb-verify
|
||||||
|
enodia-sentinel pkgdb-verify --sample 200
|
||||||
|
```
|
||||||
|
|
||||||
|
Compares on-disk package-owned files to SHA-256 hashes from `.MTREE` manifests
|
||||||
|
inside cached signed packages. This is independent of the mutable local package
|
||||||
|
database.
|
||||||
|
|
||||||
|
The command also flags insecure global `SigLevel` settings and warns if the
|
||||||
|
pacman keyring is missing.
|
||||||
|
|
||||||
|
Exit code:
|
||||||
|
|
||||||
|
- `0`: sampled files match and signature policy is not downgraded.
|
||||||
|
- `1`: a signed-package mismatch or insecure signature setting was found.
|
||||||
|
|
||||||
|
## Evidence and Operator Commands
|
||||||
|
|
||||||
|
### `web`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel web
|
||||||
|
```
|
||||||
|
|
||||||
|
Starts the read-only dashboard. By default it binds to the Tailscale interface
|
||||||
|
when available and uses bearer-token authentication for non-loopback binds.
|
||||||
|
|
||||||
|
Expected use: `enodia-sentinel-web.service`.
|
||||||
|
|
||||||
|
### `triage`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel triage
|
||||||
|
```
|
||||||
|
|
||||||
|
Reads captured alert JSON snapshots and classifies distinct findings as likely
|
||||||
|
false positive or review-needed. It suggests config snippets for known benign
|
||||||
|
noise but never edits config automatically.
|
||||||
|
|
||||||
|
### `watchdog`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel watchdog --url http://100.x.y.z:8787 --token <token> --max-age 120
|
||||||
|
```
|
||||||
|
|
||||||
|
Polls a remote dashboard and alerts if the sensor is down, unreachable, or has a
|
||||||
|
stale heartbeat. Run this from a separate host. A watchdog on the same machine
|
||||||
|
does not prove the protected host is alive.
|
||||||
|
|
||||||
|
Exit code:
|
||||||
|
|
||||||
|
- `0`: remote sensor is reachable and fresh.
|
||||||
|
- `1`: remote sensor is stale, down, or unreachable.
|
||||||
|
|
||||||
|
## Development Shortcuts
|
||||||
|
|
||||||
|
The Makefile wraps common local tasks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test
|
||||||
|
make check
|
||||||
|
make baseline
|
||||||
|
make web
|
||||||
|
make drill
|
||||||
|
```
|
||||||
|
|
||||||
|
`make install` installs a plain package directory plus a launcher wrapper. It
|
||||||
|
does not require pip or a virtualenv.
|
||||||
|
|
||||||
|
## Future Command Contracts
|
||||||
|
|
||||||
|
The roadmap reserves these command shapes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel status --json
|
||||||
|
enodia-sentinel incident list
|
||||||
|
enodia-sentinel incident show <incident-id>
|
||||||
|
enodia-sentinel incident export <incident-id>
|
||||||
|
enodia-sentinel posture check
|
||||||
|
enodia-sentinel respond plan <incident-id>
|
||||||
|
enodia-sentinel respond apply <plan-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
These should be introduced with stable JSON schemas and tests before they are
|
||||||
|
documented as supported.
|
||||||
143
docs/OPERATIONS.md
Normal file
143
docs/OPERATIONS.md
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
# Enodia Sentinel Operations Guide
|
||||||
|
|
||||||
|
This guide turns the current toolset into repeatable operator workflows. It is
|
||||||
|
deliberately practical: what to run, what to expect, and what to check next.
|
||||||
|
|
||||||
|
## First Install Checklist
|
||||||
|
|
||||||
|
1. Install and enable the service.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo make install
|
||||||
|
sudo make enable
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build the listener and SUID baselines.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo enodia-sentinel baseline
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Build the FIM baseline.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo enodia-sentinel fim-baseline
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run a one-shot check.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo enodia-sentinel check
|
||||||
|
sudo enodia-sentinel rootcheck
|
||||||
|
sudo enodia-sentinel pkgdb-check
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Start the dashboard if desired.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl enable --now enodia-sentinel-web
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Configure one off-box notification or watchdog path. A local-only alerting
|
||||||
|
path is not enough if the whole host goes silent.
|
||||||
|
|
||||||
|
## Routine Health Checks
|
||||||
|
|
||||||
|
Run these after upgrades, config edits, or suspicious behavior:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel check
|
||||||
|
enodia-sentinel fim-check
|
||||||
|
enodia-sentinel pkgdb-check
|
||||||
|
enodia-sentinel pkgdb-verify --sample 100
|
||||||
|
enodia-sentinel rootcheck
|
||||||
|
enodia-sentinel triage
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected healthy output:
|
||||||
|
|
||||||
|
- `check`: no alerts, or only expected tuned findings.
|
||||||
|
- `fim-check`: no changes against baseline after legitimate updates have been
|
||||||
|
acknowledged.
|
||||||
|
- `pkgdb-check`: package DB consistent with anchor.
|
||||||
|
- `pkgdb-verify`: sampled files match signed cache packages.
|
||||||
|
- `rootcheck`: no hidden processes, modules, ports, or sniffers.
|
||||||
|
|
||||||
|
## Alert Workflow
|
||||||
|
|
||||||
|
When Sentinel fires:
|
||||||
|
|
||||||
|
1. Open the newest JSON and text snapshots in `/var/log/enodia-sentinel`.
|
||||||
|
2. Identify the signature, `sid`, affected PIDs, executable paths, remote peers,
|
||||||
|
and parent process.
|
||||||
|
3. Run `enodia-sentinel triage` to separate known benign listener noise from
|
||||||
|
findings that need review.
|
||||||
|
4. If the alert involves a binary, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel fim-check --packages
|
||||||
|
enodia-sentinel pkgdb-verify --sample 200
|
||||||
|
```
|
||||||
|
|
||||||
|
5. If the alert involves hiding or tampering, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
enodia-sentinel rootcheck
|
||||||
|
enodia-sentinel pkgdb-check
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Preserve evidence before changing state:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp -a /var/log/enodia-sentinel /tmp/enodia-sentinel-evidence
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Contain manually for now. Future response commands should produce a dry-run
|
||||||
|
plan first.
|
||||||
|
|
||||||
|
## Common Findings
|
||||||
|
|
||||||
|
| Finding | First checks |
|
||||||
|
|---|---|
|
||||||
|
| `reverse_shell` | Inspect parent process, fd table, remote peer, shell argv, and user. |
|
||||||
|
| `egress` | Confirm interpreter, destination IP, command line, and parent process. |
|
||||||
|
| `new_listener` | Identify owning process and whether the binary is package-owned. |
|
||||||
|
| `new_suid` | Check path, owner, package provenance, and whether it lives in a writable directory. |
|
||||||
|
| `persistence` | Diff the changed file and identify the modifying package or process if possible. |
|
||||||
|
| `fim_modified` | Verify package ownership, package checksum, and whether the change followed an upgrade. |
|
||||||
|
| `pkgdb_tamper` | Treat as high-confidence tampering until a legitimate package transaction explains it. |
|
||||||
|
| `pkg_signature_mismatch` | Treat as a potentially trojaned package-owned file. |
|
||||||
|
| `hidden_*` rootcheck alerts | Compare live tools, preserve evidence, and consider offline analysis. |
|
||||||
|
|
||||||
|
## Baseline Hygiene
|
||||||
|
|
||||||
|
Baselines are useful only if they represent a known-good state.
|
||||||
|
|
||||||
|
- Build baselines after installing Sentinel on a clean host.
|
||||||
|
- Rebuild listener/SUID baselines after intentionally adding long-running
|
||||||
|
services or privileged binaries.
|
||||||
|
- Use `fim-update` only after confirming changes are legitimate. Package hooks
|
||||||
|
already run it for normal package transactions.
|
||||||
|
- Keep a copy of important anchors off-box when possible.
|
||||||
|
|
||||||
|
## Suggested Hardening
|
||||||
|
|
||||||
|
- Keep the dashboard bound to Tailscale or loopback, not a public interface.
|
||||||
|
- Set `web_token` explicitly for fully read-only service operation.
|
||||||
|
- Enable at least one push backend.
|
||||||
|
- Run `watchdog` from another machine.
|
||||||
|
- Consider `chattr +i` for Sentinel binaries, systemd units, config, baselines,
|
||||||
|
and the pacman hook after setup.
|
||||||
|
- Keep the package cache populated if using signed-package verification.
|
||||||
|
- Do not disable package signature verification.
|
||||||
|
|
||||||
|
## Evidence Export
|
||||||
|
|
||||||
|
Until the incident command set exists, an evidence bundle is simply:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tar -C /var/log -czf /tmp/enodia-sentinel-evidence.tgz enodia-sentinel
|
||||||
|
```
|
||||||
|
|
||||||
|
That bundle contains events, snapshots, baselines, heartbeat state, package DB
|
||||||
|
anchor, and dashboard-readable JSON.
|
||||||
174
docs/ROADMAP.md
Normal file
174
docs/ROADMAP.md
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
# Enodia Sentinel Roadmap
|
||||||
|
|
||||||
|
This roadmap moves Enodia Sentinel from a host IDS into a host security platform:
|
||||||
|
detect, verify, investigate, respond, and assure. Dates are intentionally not
|
||||||
|
promised here; the sequence matters more than calendar precision.
|
||||||
|
|
||||||
|
## Release Tracks
|
||||||
|
|
||||||
|
| Track | Goal |
|
||||||
|
|---|---|
|
||||||
|
| Sensor | Better host telemetry, event capture, and detection coverage. |
|
||||||
|
| Integrity | Stronger proof that files, packages, baselines, and Sentinel itself were not silently modified. |
|
||||||
|
| Incident | Group alerts, build timelines, and export evidence. |
|
||||||
|
| Response | Add safe, auditable containment and recovery workflows. |
|
||||||
|
| Fleet | Let multiple hosts report to a small controller without making the local agent dependent on it. |
|
||||||
|
| Assurance | Move trust anchors off-box or below user space. |
|
||||||
|
|
||||||
|
## v0.8: Documentation, Posture, and Incident Shape
|
||||||
|
|
||||||
|
Purpose: make the current agent easier to operate and create the data model for
|
||||||
|
work that is bigger than single alerts.
|
||||||
|
|
||||||
|
- Add `incident_id` grouping around related alerts while preserving existing
|
||||||
|
alert JSON.
|
||||||
|
- Add an incident timeline generator:
|
||||||
|
alert time, process ancestry, sockets, persistence writes, FIM diffs, package
|
||||||
|
transactions, and rootcheck findings.
|
||||||
|
- Add `enodia-sentinel incident list/show/export`.
|
||||||
|
- Add host posture checks:
|
||||||
|
SSH password login, root SSH login, permissive sudoers entries, unexpected
|
||||||
|
enabled services, world-writable PATH components, disabled package signatures,
|
||||||
|
and unsafe systemd unit settings.
|
||||||
|
- Add a machine-readable `status --json` command for automation.
|
||||||
|
- Update the red-team harness so every current detection has a named drill and
|
||||||
|
expected `sid`.
|
||||||
|
- Document incident response runbooks for reverse shells, persistence, trojaned
|
||||||
|
binaries, hidden listeners, and sensor tampering.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- One alert can become one incident artifact.
|
||||||
|
- Operators can answer "what changed around this alert?" without reading every
|
||||||
|
raw snapshot manually.
|
||||||
|
- Posture checks report reviewable findings but do not block daemon startup.
|
||||||
|
|
||||||
|
## v0.9: Response and Recovery
|
||||||
|
|
||||||
|
Purpose: move from "tell me" to "help me act" without unsafe automation.
|
||||||
|
|
||||||
|
- Add response plan generation:
|
||||||
|
`enodia-sentinel respond plan <incident-id>`.
|
||||||
|
- Add dry-run first actions:
|
||||||
|
kill process, stop/disable service, block remote IP, quarantine file, restore
|
||||||
|
package-owned file by reinstalling its package, and freeze evidence.
|
||||||
|
- Require explicit `--apply` for changes; default to read-only plans.
|
||||||
|
- Write response audit logs under the normal log directory.
|
||||||
|
- Add baseline reconciliation:
|
||||||
|
accept legitimate FIM/package/listener/SUID changes with a recorded reason.
|
||||||
|
- Add richer false-positive suppression suggestions that can emit TOML snippets
|
||||||
|
but never modify config automatically.
|
||||||
|
- Add recovery checks:
|
||||||
|
"verify package", "re-run rootcheck", "confirm no persistence diff", and
|
||||||
|
"confirm heartbeat/dashboard visible from watchdog".
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- An operator can go from alert to reviewed response plan with one command.
|
||||||
|
- Any state-changing action is explicit, logged, reversible where possible, and
|
||||||
|
testable in a safe fixture.
|
||||||
|
|
||||||
|
## v1.0: Stable Local Platform
|
||||||
|
|
||||||
|
Purpose: define a stable single-host product.
|
||||||
|
|
||||||
|
- Commit to stable JSON schemas for alerts, incidents, status, and response
|
||||||
|
audits.
|
||||||
|
- Add compatibility tests for schema evolution.
|
||||||
|
- Add documentation versioning and manpage-style command reference.
|
||||||
|
- Harden packaging for Arch first, then document Debian/RPM install paths.
|
||||||
|
- Add signed release artifacts and checksums.
|
||||||
|
- Improve dashboard from alert browser to local console:
|
||||||
|
incidents, posture, integrity state, response plans, and watchdog status.
|
||||||
|
- Keep the dashboard read-only unless a separate authenticated write path is
|
||||||
|
designed and reviewed.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- The local agent can be installed, operated, upgraded, and integrated without
|
||||||
|
reading source code.
|
||||||
|
- JSON and command contracts are stable enough for downstream automation.
|
||||||
|
|
||||||
|
## v1.1: Event Coverage and Correlation
|
||||||
|
|
||||||
|
Purpose: reduce blind spots and connect related signals.
|
||||||
|
|
||||||
|
- Add eBPF event sources:
|
||||||
|
`tcp_connect`, `bind`, `accept`, module load, privilege transitions, and
|
||||||
|
writes to watched persistence paths.
|
||||||
|
- Build process lineage across polling and event sources.
|
||||||
|
- Correlate multi-stage behavior:
|
||||||
|
web service spawns shell, shell downloads payload, payload adds persistence,
|
||||||
|
listener appears, and FIM changes.
|
||||||
|
- Add configurable correlation windows and severity escalation rules.
|
||||||
|
- Keep polling as the oracle and fallback.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Short-lived network and persistence actions are visible.
|
||||||
|
- Related alerts collapse into one incident with a readable timeline.
|
||||||
|
|
||||||
|
## v1.2: Fleet Mode
|
||||||
|
|
||||||
|
Purpose: support several personal or small-business Linux hosts without turning
|
||||||
|
the agent into a heavy enterprise platform.
|
||||||
|
|
||||||
|
- Add an optional collector service.
|
||||||
|
- Agents push signed JSON events and heartbeats over HTTPS or a tailnet.
|
||||||
|
- Collector stores host status, alerts, incidents, and package/integrity state.
|
||||||
|
- Add host enrollment with per-host tokens.
|
||||||
|
- Add fleet-wide views:
|
||||||
|
stale sensors, repeated signatures, hosts missing package signatures, and
|
||||||
|
integrity drift.
|
||||||
|
- Preserve local autonomy: detection and evidence capture must continue if the
|
||||||
|
collector is down.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- A small fleet can answer "which host is compromised or silent?" from one
|
||||||
|
console.
|
||||||
|
- The agent still works as a standalone local tool.
|
||||||
|
|
||||||
|
## v1.3+: Assurance and Hardening
|
||||||
|
|
||||||
|
Purpose: make tampering harder to hide from an attacker with high privileges.
|
||||||
|
|
||||||
|
- External baseline anchor:
|
||||||
|
mirror FIM and package DB fingerprints off-box with append-only semantics.
|
||||||
|
- Hash-chain `events.log` and snapshots.
|
||||||
|
- Optional snapshot signing with a host key.
|
||||||
|
- Investigate Linux IMA/EVM and TPM-backed attestation for supported machines.
|
||||||
|
- Replace bcc with a libbpf + CO-RE agent if the event layer becomes core to
|
||||||
|
the product.
|
||||||
|
- Explore minimal kernel-side enforcement hooks for response actions, while
|
||||||
|
keeping the default product inspect-only.
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- A local root attacker has to tamper with both the host and an external or
|
||||||
|
hardware-backed trust anchor to hide compromise.
|
||||||
|
|
||||||
|
## Backlog
|
||||||
|
|
||||||
|
Useful ideas that need design before implementation:
|
||||||
|
|
||||||
|
- Policy-as-code for host posture.
|
||||||
|
- YARA-compatible scanning for selected paths.
|
||||||
|
- Sigma-like host event rules.
|
||||||
|
- SBOM and package provenance reporting.
|
||||||
|
- Offline evidence bundle for outside analysis.
|
||||||
|
- Dashboard import of exported incident bundles.
|
||||||
|
- Support for non-pacman package managers in signed-package verification.
|
||||||
|
- Dedicated Debian/RPM packaging.
|
||||||
|
- Installer preflight checks and health diagnostics.
|
||||||
|
- Rule documentation generated from source defaults.
|
||||||
|
|
||||||
|
## Roadmap Rules
|
||||||
|
|
||||||
|
- Do not add dependencies to the base daemon without a specific security or
|
||||||
|
operator value that cannot be achieved with stdlib.
|
||||||
|
- Prefer read-only detection and dry-run response until the command contract is
|
||||||
|
tested and documented.
|
||||||
|
- Every new detector or response action needs a safe drill or fixture.
|
||||||
|
- Do not build stealth or self-hiding behavior.
|
||||||
|
- Keep the local agent useful without a dashboard, collector, or network.
|
||||||
206
docs/SPECIFICATION.md
Normal file
206
docs/SPECIFICATION.md
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
# Enodia Sentinel Specification
|
||||||
|
|
||||||
|
This document defines Enodia Sentinel as a host security platform, not only a
|
||||||
|
signature IDS. The current implementation is a local Linux sensor with evidence
|
||||||
|
capture, integrity monitoring, anti-rootkit checks, a dashboard, and alert
|
||||||
|
delivery. The intended product is a defensive control plane for a host or small
|
||||||
|
fleet: detect compromise, prove what changed, preserve useful evidence, guide
|
||||||
|
response, and make sensor tampering visible.
|
||||||
|
|
||||||
|
## Product Definition
|
||||||
|
|
||||||
|
Enodia Sentinel is a zero-runtime-dependency Linux host security agent. It runs
|
||||||
|
as a local daemon, observes high-signal host behavior, records forensic context
|
||||||
|
when something suspicious happens, and exposes the result through local files,
|
||||||
|
JSON, a read-only dashboard, push notifications, and command-line tools.
|
||||||
|
|
||||||
|
The long-term product should combine five functions:
|
||||||
|
|
||||||
|
| Function | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| Detection | Find active compromise behavior: reverse shells, fileless execution, new listeners, suspicious egress, persistence edits, and privilege-escalation artifacts. |
|
||||||
|
| Integrity | Prove whether important files, packages, baselines, and Sentinel's own footprint changed. |
|
||||||
|
| Evidence | Capture enough context for incident response without forcing the operator to reproduce a live state after the attacker is gone. |
|
||||||
|
| Response | Provide safe, explicit operator workflows for triage, containment, and recovery. |
|
||||||
|
| Assurance | Make the agent itself measurable: heartbeat, dead-man's switch, tamper-evidence, and future remote attestation. |
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
1. **High-signal first.** Prefer detections that are cheap to observe and costly
|
||||||
|
for attackers to avoid, even if that means a smaller rule set.
|
||||||
|
2. **Evidence over noise.** Every alert should carry enough context to answer
|
||||||
|
"what happened, where, and what should I inspect next?"
|
||||||
|
3. **Zero dependency core.** The base daemon must run with the Python standard
|
||||||
|
library only. Optional layers such as bcc/eBPF may degrade cleanly.
|
||||||
|
4. **One local trust boundary is not enough.** On-box checks are useful but
|
||||||
|
root can tamper with them. Important anchors should move to signed package
|
||||||
|
metadata, immutable filesystem controls, external watchers, or future
|
||||||
|
remote attestation.
|
||||||
|
5. **Operator control.** Sentinel should recommend response actions before it
|
||||||
|
takes them. Destructive containment must be opt-in and auditable.
|
||||||
|
6. **Bash prototype remains the oracle.** The original shell implementation and
|
||||||
|
red-team harness preserve behavioral compatibility for core signatures.
|
||||||
|
|
||||||
|
## Current Scope: v0.7
|
||||||
|
|
||||||
|
### Poll Detectors
|
||||||
|
|
||||||
|
The daemon builds one `SystemState` per sweep and runs pure detector functions
|
||||||
|
against cached process and socket data:
|
||||||
|
|
||||||
|
| Detector | Signal |
|
||||||
|
|---|---|
|
||||||
|
| `reverse_shell` | Interpreter with a network socket on fd 0/1/2. |
|
||||||
|
| `ld_preload` | Non-empty `/etc/ld.so.preload` or writable-path `LD_PRELOAD`. |
|
||||||
|
| `deleted_exe` | Process executing from a deleted file or `memfd:` image. |
|
||||||
|
| `new_listener` | Listening socket absent from the startup baseline. |
|
||||||
|
| `new_suid` | New SUID/SGID binary, critical in writable locations. |
|
||||||
|
| `persistence` | Changes to cron, systemd units, SSH keys, shell rc files, and similar persistence locations. |
|
||||||
|
| `egress` | Interpreter with an established connection to a public IP. |
|
||||||
|
|
||||||
|
### Event Detection
|
||||||
|
|
||||||
|
An optional bcc eBPF `execve` monitor feeds a declarative rule engine. This
|
||||||
|
closes the polling gap for short-lived commands and supports custom TOML rules
|
||||||
|
without code changes. Failure is fail-safe: if the probe cannot load, polling
|
||||||
|
continues.
|
||||||
|
|
||||||
|
### Integrity and Tamper-Evidence
|
||||||
|
|
||||||
|
Sentinel includes several integrity layers:
|
||||||
|
|
||||||
|
| Layer | Current behavior |
|
||||||
|
|---|---|
|
||||||
|
| FIM baseline | SHA-256 baseline for critical files and Sentinel's own footprint. |
|
||||||
|
| Package verification | Optional `pacman -Qkk` verification for package-owned files. |
|
||||||
|
| Package DB anchor | Detects out-of-band edits to `/var/lib/pacman/local`. |
|
||||||
|
| Signed-package anchor | Compares on-disk files to `.MTREE` hashes from cached signed packages. |
|
||||||
|
| Heartbeat | Writes daemon liveness for local dashboard and external watchdog use. |
|
||||||
|
| Rootcheck | Cross-view checks for hidden processes, hidden modules, hidden ports, and promiscuous interfaces. |
|
||||||
|
|
||||||
|
### Evidence Capture
|
||||||
|
|
||||||
|
When a fresh alert survives cooldown, Sentinel writes:
|
||||||
|
|
||||||
|
- A human-readable snapshot.
|
||||||
|
- A JSON snapshot suitable for automation and SIEM import.
|
||||||
|
- Relevant process tree, sockets, environment hints, file descriptors, and
|
||||||
|
response guidance.
|
||||||
|
- Event log entries and dashboard-visible status.
|
||||||
|
|
||||||
|
### Operator Interfaces
|
||||||
|
|
||||||
|
| Interface | Role |
|
||||||
|
|---|---|
|
||||||
|
| CLI | Run daemon, one-shot checks, baseline management, FIM checks, package DB checks, rootcheck, triage, watchdog. |
|
||||||
|
| Logs | Durable local evidence under `/var/log/enodia-sentinel`. |
|
||||||
|
| Dashboard | Read-only web view for status, alert browsing, and snapshot inspection. |
|
||||||
|
| Push | ntfy, Pushover, and generic webhook notifications. |
|
||||||
|
| Red-team harness | Safe drills for testing signatures and demos. |
|
||||||
|
|
||||||
|
## Target Scope: Host Security Platform
|
||||||
|
|
||||||
|
The next product shape should make Enodia useful before, during, and after an
|
||||||
|
incident.
|
||||||
|
|
||||||
|
### Prevent
|
||||||
|
|
||||||
|
- Configuration posture checks for risky SSH, sudo, systemd, cron, kernel,
|
||||||
|
package manager, firewall, and service settings.
|
||||||
|
- Baseline drift reports for newly exposed services, new SUID files, new
|
||||||
|
autostarts, new users/groups, and sensitive permission changes.
|
||||||
|
- A policy mode that can fail CI or package build checks for unsafe host images.
|
||||||
|
|
||||||
|
### Detect
|
||||||
|
|
||||||
|
- More event sources: `tcp_connect`, `bind`, `accept`, file-write events for
|
||||||
|
persistence paths, module loading, privilege transitions, and LSM hooks.
|
||||||
|
- Correlation across signals: "web service spawned shell" + "new listener" +
|
||||||
|
"persistence write" should become one incident, not three unrelated alerts.
|
||||||
|
- Optional YARA-style and Sigma-like local rules if they can stay dependency
|
||||||
|
light and operator-readable.
|
||||||
|
|
||||||
|
### Investigate
|
||||||
|
|
||||||
|
- Incident timeline files derived from event log, snapshots, FIM diffs,
|
||||||
|
package transactions, process lineage, and network observations.
|
||||||
|
- Stable incident IDs that group related alerts over time.
|
||||||
|
- `enodia-sentinel incident show <id>` and `incident export <id>` workflows.
|
||||||
|
|
||||||
|
### Respond
|
||||||
|
|
||||||
|
- Safe containment actions behind explicit flags:
|
||||||
|
stop a process, disable a systemd unit, remove a persistence entry, block an
|
||||||
|
IP with nftables, quarantine a file, or freeze evidence.
|
||||||
|
- Dry-run by default, with JSON plans that can be reviewed before execution.
|
||||||
|
- Response playbooks that are code-reviewed data files, not shell snippets
|
||||||
|
pasted into documentation.
|
||||||
|
|
||||||
|
### Recover
|
||||||
|
|
||||||
|
- Package restore guidance: identify owning package, verify cache signature,
|
||||||
|
reinstall package, and re-run integrity checks.
|
||||||
|
- Baseline reconciliation workflow after confirmed legitimate changes.
|
||||||
|
- Post-incident report generation from captured evidence.
|
||||||
|
|
||||||
|
### Assure
|
||||||
|
|
||||||
|
- External anchor storage for baselines and package DB fingerprints.
|
||||||
|
- Signed snapshots or hash-chained event logs.
|
||||||
|
- Remote watchdog improvements and eventually fleet controller support.
|
||||||
|
- Optional TPM/IMA integration for machines that can support measured boot.
|
||||||
|
|
||||||
|
## Data Model
|
||||||
|
|
||||||
|
The current `Alert` object is the public detection unit:
|
||||||
|
|
||||||
|
| Field | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `sid` | Stable signature ID for tuning, documentation, and downstream rules. |
|
||||||
|
| `severity` | `MEDIUM`, `HIGH`, or `CRITICAL`. |
|
||||||
|
| `signature` | Stable machine-readable detection name. |
|
||||||
|
| `classtype` | Snort/Suricata-style category. |
|
||||||
|
| `key` | Cooldown and deduplication identity. |
|
||||||
|
| `detail` | Human-readable explanation. |
|
||||||
|
| `pids` | Process IDs for snapshot deep dives. |
|
||||||
|
|
||||||
|
Future incident grouping should add an `incident_id` around one or more alerts,
|
||||||
|
without breaking the alert JSON schema.
|
||||||
|
|
||||||
|
## Configuration Model
|
||||||
|
|
||||||
|
Configuration is a TOML file loaded at startup. Unknown keys are ignored so
|
||||||
|
newer config templates remain tolerable on older agents, but documentation
|
||||||
|
should clearly mark version-specific fields. Existing defaults should prefer
|
||||||
|
detection correctness over suppression. Convenience suppression knobs should
|
||||||
|
remain explicit and reviewable.
|
||||||
|
|
||||||
|
## Security Model
|
||||||
|
|
||||||
|
Sentinel runs as root because it must inspect root-owned files, process state,
|
||||||
|
file descriptors, and sockets. The systemd unit should continue to keep the
|
||||||
|
root process narrow: read-mostly, constrained writable paths, no new privileges,
|
||||||
|
restricted namespaces, and only the capabilities needed for the enabled layers.
|
||||||
|
|
||||||
|
Local root can eventually defeat any purely local sensor. Sentinel's answer is
|
||||||
|
layering: signed package metadata, immutable files, off-box watchdogs, external
|
||||||
|
anchors, and future kernel/attestation features.
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
- Replacing a full enterprise SIEM.
|
||||||
|
- Silent self-hiding or rootkit-like persistence.
|
||||||
|
- Automatic destructive remediation without explicit operator approval.
|
||||||
|
- Shipping a broad dependency tree in the default agent.
|
||||||
|
- Claiming complete protection against a custom kernel implant.
|
||||||
|
|
||||||
|
## Acceptance Criteria For New Platform Features
|
||||||
|
|
||||||
|
A feature is ready when it has:
|
||||||
|
|
||||||
|
- A stable `sid`/signature or documented command contract.
|
||||||
|
- Unit tests for pure logic and a safe integration path where practical.
|
||||||
|
- JSON output or a durable artifact suitable for automation.
|
||||||
|
- Configuration documented in the sample TOML and README or relevant docs.
|
||||||
|
- Failure behavior that does not take down the daemon.
|
||||||
|
- Clear operator guidance for false positives, limitations, and response.
|
||||||
119
docs/THREAT_MODEL.md
Normal file
119
docs/THREAT_MODEL.md
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
# Enodia Sentinel Threat Model
|
||||||
|
|
||||||
|
This document describes what Enodia Sentinel is meant to catch, what it can only
|
||||||
|
make evident, and what it does not claim to solve.
|
||||||
|
|
||||||
|
## Assets
|
||||||
|
|
||||||
|
Sentinel protects and records evidence about:
|
||||||
|
|
||||||
|
- Running processes and process ancestry.
|
||||||
|
- Network sockets and remote peers.
|
||||||
|
- File descriptors and executable paths.
|
||||||
|
- SUID/SGID inventory and privilege-escalation artifacts.
|
||||||
|
- Persistence locations: cron, systemd units, SSH keys, shell startup files, and
|
||||||
|
sensitive account/sudo configuration.
|
||||||
|
- Package-owned binaries and local package database integrity.
|
||||||
|
- Sentinel's own binaries, config, systemd units, hook, baselines, logs, and
|
||||||
|
heartbeat.
|
||||||
|
|
||||||
|
## Adversary Model
|
||||||
|
|
||||||
|
Sentinel is designed to help against:
|
||||||
|
|
||||||
|
| Adversary | Examples | Expected Sentinel value |
|
||||||
|
|---|---|---|
|
||||||
|
| Opportunistic remote shell | Webshell, exposed service RCE, stolen SSH key | Detect reverse shell, suspicious egress, new listeners, persistence writes. |
|
||||||
|
| Local privilege escalation | New SUID helper, dropped setuid shell, writable path abuse | Detect new SUID/SGID and critical writable-directory placement. |
|
||||||
|
| Fileless or short-lived execution | Deleted executable, memfd payload, fast `curl|sh` | Detect deleted executables and eBPF exec rules where available. |
|
||||||
|
| Package/file tampering | Trojaned binary, rewritten package DB checksums | Detect FIM drift, package DB tamper, and signed-package mismatches. |
|
||||||
|
| Common rootkit hiding | LD_PRELOAD tricks, `/proc` hiding, module-list hiding, hidden listener | Detect LD_PRELOAD and cross-view inconsistencies. |
|
||||||
|
| Sensor tampering | Stop daemon, edit config, remove hook, modify baseline | Detect self-integrity changes and stale heartbeat via external watchdog. |
|
||||||
|
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
### Local Root Is Not Fully Trusted
|
||||||
|
|
||||||
|
The daemon runs as root so it can inspect the host. If an attacker gains root,
|
||||||
|
they share that privilege. Sentinel therefore does not claim that local files,
|
||||||
|
local baselines, or local process views are unforgeable.
|
||||||
|
|
||||||
|
The design goal is layered tamper-evidence:
|
||||||
|
|
||||||
|
- Package signatures are harder for a local attacker to forge than the local
|
||||||
|
package DB.
|
||||||
|
- Immutable bits force visible state changes before local files can be edited.
|
||||||
|
- External watchdogs notice silence.
|
||||||
|
- Future external anchors and signed logs should make retroactive rewriting
|
||||||
|
harder.
|
||||||
|
|
||||||
|
### Optional eBPF Expands Privilege
|
||||||
|
|
||||||
|
The default service is hardened and read-mostly. Enabling the bcc eBPF monitor
|
||||||
|
requires additional capabilities and memory permissions. That tradeoff is
|
||||||
|
explicit: stronger event visibility for a wider runtime permission set.
|
||||||
|
|
||||||
|
## Detection Assumptions
|
||||||
|
|
||||||
|
Sentinel assumes:
|
||||||
|
|
||||||
|
- Attackers need a process, socket, file, package, persistence location, or
|
||||||
|
privilege transition somewhere on the host.
|
||||||
|
- High-confidence host behaviors are more useful than broad noisy rules.
|
||||||
|
- Many attacks are visible from more than one view: `/proc`, `/sys`, `ss`,
|
||||||
|
package metadata, logs, and filesystem hashes.
|
||||||
|
- Snapshots captured immediately are more useful than asking an operator to
|
||||||
|
reconstruct state after the process exits.
|
||||||
|
|
||||||
|
## Important Limitations
|
||||||
|
|
||||||
|
- A custom kernel implant that hooks every view consistently can evade
|
||||||
|
user-space cross-view checks.
|
||||||
|
- A local root attacker can delete or rewrite local logs unless external anchors
|
||||||
|
or immutable controls are in place.
|
||||||
|
- Signed-package verification depends on a populated package cache and valid
|
||||||
|
package signature policy.
|
||||||
|
- `pacman -Qkk` trusts the local package database; it is useful but not an
|
||||||
|
independent root of trust.
|
||||||
|
- The dashboard is read-only by design; it is not a remote response console.
|
||||||
|
- The current product does not perform automatic containment.
|
||||||
|
|
||||||
|
## Security Controls Already Present
|
||||||
|
|
||||||
|
| Control | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| Hardened systemd unit | Limits daemon write surface and runtime privilege where possible. |
|
||||||
|
| Cooldown deduplication | Prevents alert storms from one persistent condition. |
|
||||||
|
| Snapshot retention | Preserves evidence while bounding local disk growth. |
|
||||||
|
| FIM self-watch | Detects changes to Sentinel's own files and service definitions. |
|
||||||
|
| Package DB anchor | Flags package checksum DB edits outside package transactions. |
|
||||||
|
| Signed-package verification | Checks files against package manifests independent of the local DB. |
|
||||||
|
| Rootcheck | Finds common hiding artifacts by comparing independent views. |
|
||||||
|
| Heartbeat + watchdog | Makes a silent sensor observable from another machine. |
|
||||||
|
| Read-only dashboard | Exposes evidence without adding a remote write path. |
|
||||||
|
|
||||||
|
## Response Safety
|
||||||
|
|
||||||
|
Sentinel should remain conservative:
|
||||||
|
|
||||||
|
- Detection may be automatic.
|
||||||
|
- Evidence capture may be automatic.
|
||||||
|
- Notification may be automatic.
|
||||||
|
- Triage suggestions may be automatic.
|
||||||
|
- Destructive response should require an explicit operator action.
|
||||||
|
|
||||||
|
Future response features should produce dry-run plans first, log all changes,
|
||||||
|
and avoid shell-string execution where structured APIs exist.
|
||||||
|
|
||||||
|
## Abuse Considerations
|
||||||
|
|
||||||
|
Some security tools can become dual-use. Enodia should avoid:
|
||||||
|
|
||||||
|
- Stealth, hiding, or rootkit-like self-defense.
|
||||||
|
- Persistence mechanisms that are hard for the owner to inspect.
|
||||||
|
- Automated deletion of suspicious files without review.
|
||||||
|
- Covert exfiltration of host data.
|
||||||
|
- Cloud dependence for local detection.
|
||||||
|
|
||||||
|
The correct defensive posture is visibility, evidence, and explicit operator
|
||||||
|
control.
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
"""Enodia Sentinel — a host intrusion-detection daemon.
|
"""Enodia Sentinel — a Linux host security platform.
|
||||||
|
|
||||||
A poll-based HIDS that runs a set of detectors over live system state and
|
Detection, file/package integrity, anti-rootkit checks, tamper-evidence,
|
||||||
captures a forensic snapshot with incident-response guidance whenever a known
|
forensic snapshots, and operator workflows. The Python re-architecture of the
|
||||||
attack signature appears. The Python re-architecture of the bash v0 prototype.
|
bash v0 prototype.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "0.7.0"
|
__version__ = "0.7.0"
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ def _cmd_check(cfg: Config) -> int:
|
||||||
def main(argv: list[str] | None = None) -> int:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
prog="enodia-sentinel",
|
prog="enodia-sentinel",
|
||||||
description="Host intrusion-detection daemon.",
|
description="Linux host security platform.",
|
||||||
)
|
)
|
||||||
parser.add_argument("--version", action="version",
|
parser.add_argument("--version", action="version",
|
||||||
version=f"enodia-sentinel {__version__}")
|
version=f"enodia-sentinel {__version__}")
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,8 @@ def siglevel_insecure(value: str) -> bool:
|
||||||
|
|
||||||
def siglevel_alert(conf_path: str = PACMAN_CONF) -> Alert | None:
|
def siglevel_alert(conf_path: str = PACMAN_CONF) -> Alert | None:
|
||||||
try:
|
try:
|
||||||
text = open(conf_path, encoding="utf-8", errors="replace").read()
|
with open(conf_path, encoding="utf-8", errors="replace") as fh:
|
||||||
|
text = fh.read()
|
||||||
except OSError:
|
except OSError:
|
||||||
return None
|
return None
|
||||||
value = parse_global_siglevel(text)
|
value = parse_global_siglevel(text)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
||||||
[project]
|
[project]
|
||||||
name = "enodia-sentinel"
|
name = "enodia-sentinel"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
description = "Host intrusion-detection daemon — signature-based detection of reverse shells, LD_PRELOAD rootkits, fileless malware, and persistence tampering"
|
description = "Linux host security platform — detection, integrity, anti-rootkit checks, tamper-evidence, and forensic snapshots"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
license = { text = "GPL-3.0-or-later" }
|
license = { text = "GPL-3.0-or-later" }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue