508 lines
16 KiB
Markdown
508 lines
16 KiB
Markdown
# Enodia Sentinel Command Reference
|
|
|
|
Applies to package version: `0.7.0`
|
|
Docs version: `0.7`
|
|
Schema generation: `v1`
|
|
|
|
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 exec/syscall event monitoring, writes
|
|
heartbeats, runs detector sweeps, captures snapshots, and sends notifications.
|
|
|
|
Expected use: systemd, not an interactive shell.
|
|
|
|
### `check`
|
|
|
|
```bash
|
|
enodia-sentinel check
|
|
enodia-sentinel check --json
|
|
```
|
|
|
|
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. `--json` emits the alerts as a JSON array of alert dicts
|
|
(severity-ordered) instead of text, for scripts and the tray applet.
|
|
|
|
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.
|
|
|
|
### `baseline accept` / `revoke` / `list`
|
|
|
|
```bash
|
|
enodia-sentinel baseline accept <kind> <target> --reason "..." [--expires 7d] [--force]
|
|
enodia-sentinel baseline revoke <kind> <target>
|
|
enodia-sentinel baseline list [--stale] [--json]
|
|
```
|
|
|
|
Baseline *reconciliation*: accept a specific, identified drift item with a
|
|
mandatory reason instead of silently folding it into a baseline. `<kind>` is one
|
|
of `fim`, `pkgfile`, `listener`, or `suid`; `<target>` is a path (FIM/package/
|
|
SUID) or a `port/comm` key (listener).
|
|
|
|
An acknowledgement suppresses that one alert **only while the live state still
|
|
matches the accepted fingerprint**. `fim` and `pkgfile` acks are content-bound:
|
|
when the file changes again the ack goes *stale* and the alert returns. `listener`
|
|
and `suid` acks are identity-bound, so only `--expires` (TTL) or an explicit
|
|
`revoke` retires them. Each record stores who accepted it, when, why, and the
|
|
fingerprint, in `log_dir/reconciliation.json` (override with `reconcile_store`).
|
|
|
|
- `accept` refuses to record drift it cannot observe live (e.g. a listener not
|
|
currently bound); pass `--force` to accept anyway — required for accepting a
|
|
*removed* file. Omitting `--reason` is a hard error.
|
|
- `--expires` accepts `Nd` / `Nh` / `Nm` (days/hours/minutes).
|
|
- `revoke` exits `1` if the target isn't present.
|
|
- `list` shows every ack; `--stale` filters to stale/expired ones. It exits `1`
|
|
whenever any acknowledgement is stale (a scriptable drift check) and re-derives
|
|
FIM staleness against live files.
|
|
|
|
Reconciled drift is dropped at the daemon detection chokepoint, hidden from
|
|
`fim-check`, and summarised in the dashboard integrity view, so an acknowledged
|
|
item stops alerting everywhere until its fingerprint changes or its TTL lapses.
|
|
|
|
## Detection and Integrity Commands
|
|
|
|
### `list-detectors`
|
|
|
|
```bash
|
|
enodia-sentinel list-detectors
|
|
```
|
|
|
|
Prints the poll detectors and whether each is enabled by config.
|
|
|
|
### `rules`
|
|
|
|
```bash
|
|
enodia-sentinel rules list
|
|
enodia-sentinel rules list --json
|
|
enodia-sentinel rules show <sid>
|
|
enodia-sentinel rules show <sid> --json
|
|
enodia-sentinel rules test <event-json>
|
|
enodia-sentinel rules test <event-json> --json
|
|
enodia-sentinel rules docs
|
|
```
|
|
|
|
Inspects and tests event-driven detection rules without reading source code.
|
|
`rules list` includes built-in exec/syscall rules plus configured exec rules
|
|
from `exec_rules_file`. `rules show <sid>` prints the rule metadata and match
|
|
conditions. `rules test <event-json>` loads an exec or syscall event JSON file,
|
|
runs the matching rule engine, and prints any alerts that would fire. Use `-`
|
|
instead of a path to read the event JSON from stdin. `rules docs` emits Markdown
|
|
rule documentation from the active rule metadata; the built-in snapshot lives in
|
|
[RULES.md](RULES.md).
|
|
|
|
Exec event JSON shape:
|
|
|
|
```json
|
|
{
|
|
"pid": 42,
|
|
"ppid": 1,
|
|
"uid": 1000,
|
|
"parent_comm": "nginx",
|
|
"filename": "/bin/sh",
|
|
"argv": ["-c", "id"]
|
|
}
|
|
```
|
|
|
|
Syscall event JSON shape:
|
|
|
|
```json
|
|
{
|
|
"pid": 42,
|
|
"ppid": 1,
|
|
"uid": 1000,
|
|
"comm": "payload",
|
|
"syscall": "mprotect",
|
|
"args": [0, 4096, 6, 0, 0, 0]
|
|
}
|
|
```
|
|
|
|
Exit code:
|
|
|
|
- `0`: list/show succeeded, or `rules test` matched at least one rule.
|
|
- `1`: unknown sid for `show`, or no rule matched for `test`.
|
|
- `2`: malformed sid, missing target, unreadable JSON, or unsupported event
|
|
shape.
|
|
|
|
### `rootcheck`
|
|
|
|
```bash
|
|
enodia-sentinel rootcheck
|
|
```
|
|
|
|
Runs anti-rootkit cross-view checks once:
|
|
|
|
- PIDs alive via `kill(pid, 0)` but missing from `/proc`.
|
|
- PIDs visible in `/proc` but missing from `ps`.
|
|
- Live modules in `/sys/module` but missing from `/proc/modules`.
|
|
- Listening TCP ports in `/proc/net/tcp*` but missing from `ss`.
|
|
- UDP ports in `/proc/net/udp*` but missing from `ss -u`.
|
|
- Raw and special-protocol sockets in `/proc/net` but missing from `ss`.
|
|
- Network interfaces in promiscuous mode.
|
|
- Known rootkit module names.
|
|
- Tainted loaded modules (`/sys/module/*/taint`) and global kernel taint
|
|
(`/proc/sys/kernel/tainted`).
|
|
|
|
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.
|
|
|
|
### `incident`
|
|
|
|
```bash
|
|
enodia-sentinel incident list [--json]
|
|
enodia-sentinel incident show <incident-id> [--json]
|
|
enodia-sentinel incident export <incident-id>
|
|
```
|
|
|
|
Groups related alerts into incidents so one intrusion reads as one story instead
|
|
of several disconnected alerts. Grouping is **process-lineage first** (alerts
|
|
sharing a PID or a common ancestor — the web server or SSH session they descend
|
|
from), with a **time-window fallback** for alerts that carry no live PID (FIM
|
|
drift, package tamper, hidden modules). Each captured snapshot records an
|
|
`incident_id`; the index lives in `incidents.json` under `log_dir`.
|
|
|
|
- `list` — incidents newest-first: id, last seen, severity, alert count, and the
|
|
signatures involved.
|
|
- `show <id>` — incident summary plus a time-ordered timeline of its member
|
|
snapshots (time, severity, signatures, PIDs, and the snapshot file to open).
|
|
- `export <id>` — a single JSON bundle of the incident record with every member
|
|
snapshot report inlined, suitable for handoff or offline analysis.
|
|
|
|
Tuning lives in config: `incident_window` (how long an incident stays open for
|
|
new alerts) and `incident_lineage_depth` (ancestors walked when correlating).
|
|
|
|
Exit code: `0` on success; `1` if the incident id is unknown; `2` if `show`/
|
|
`export` is called without an id.
|
|
|
|
### `posture check`
|
|
|
|
```bash
|
|
enodia-sentinel posture check
|
|
enodia-sentinel posture check --json
|
|
```
|
|
|
|
Audits host configuration hygiene — the conditions that make a host *easy* to
|
|
attack, as opposed to an attack in progress:
|
|
|
|
- SSH: root login, password authentication, and empty-password logins.
|
|
- sudo: passwordless (`NOPASSWD`) rules, disabled authentication, and
|
|
group/world-writable sudoers files.
|
|
- World-writable or non-root-owned `PATH` directories (binary-hijack vectors).
|
|
- Loose permissions on sensitive files (`/etc/shadow`, `/etc/passwd`, ...).
|
|
- Enabled systemd units with group/world-writable unit files, or whose
|
|
`ExecStart` binary runs from a writable/unsafe path (`/tmp`, `/dev/shm`,
|
|
`/var/tmp`, `/home`, `/run/user`) — a common persistence/hijack vector.
|
|
Shells out to `systemctl` with timeouts and fails closed on non-systemd
|
|
hosts; disable with `posture_systemd = false`.
|
|
- Downgraded package-signature policy (`SigLevel`).
|
|
|
|
Findings are advisory and reuse the standard alert JSON shape (`--json`). This
|
|
command does not run inside the daemon and never blocks startup.
|
|
|
|
Exit code:
|
|
|
|
- `0`: no posture findings.
|
|
- `1`: one or more findings.
|
|
|
|
## Evidence and Operator Commands
|
|
|
|
### `respond`
|
|
|
|
```bash
|
|
enodia-sentinel respond plan <incident-id>
|
|
enodia-sentinel respond plan <incident-id> --json
|
|
```
|
|
|
|
Builds a dry-run response plan from an incident's retained JSON snapshots. The
|
|
plan may include evidence preservation, process freeze/terminate candidates,
|
|
outbound IP blocks, systemd unit disablement, suspicious-file quarantine,
|
|
package-restore planning, and follow-up verification checks. Package restore
|
|
guidance stays explicit: identify the owner with `pacman -Qo`, verify signed
|
|
package-cache metadata with `pkgdb-verify`, reinstall the owner from trusted
|
|
package media, then re-run `fim-check --packages` and `fim-update` only after
|
|
operator review. Recovery checks stay read-only too: plans can ask the operator
|
|
to re-run `rootcheck`, confirm persistence alerts are gone with `check --json`,
|
|
and run `watchdog` from another host to prove the heartbeat/dashboard path is
|
|
visible.
|
|
|
|
This command is intentionally read-only: it prints commands for operator review
|
|
and never executes them. Each CLI-generated plan is also saved under
|
|
`<log_dir>/response-plans/`, and a compact JSONL audit record is appended to
|
|
`<log_dir>/response-audit.log` with the plan id, incident id, action count, and
|
|
saved path. The JSON schema is `enodia.response.plan.v1` and marks
|
|
`apply_supported: false` until an audited apply workflow exists. Dashboard/API
|
|
plan previews remain read-only and do not create new artifacts.
|
|
|
|
Exit code:
|
|
|
|
- `0`: plan generated.
|
|
- `1`: unknown incident id.
|
|
- `2`: missing incident id.
|
|
|
|
### `status`
|
|
|
|
```bash
|
|
enodia-sentinel status
|
|
enodia-sentinel status --json
|
|
```
|
|
|
|
Prints a local health and alert summary: daemon running state, heartbeat age and
|
|
staleness, total alerts with per-severity counts, last alert time, and eBPF
|
|
monitor state. `--json` emits the same data as the dashboard `/api/status`
|
|
endpoint, for cron/monitoring.
|
|
|
|
Stable JSON contracts are documented in [SCHEMAS.md](SCHEMAS.md). Current v1
|
|
IDs cover alert objects/snapshots, incident records/views/exports, status,
|
|
response plans, and response audit JSONL records.
|
|
|
|
Exit code:
|
|
|
|
- `0`: daemon is running and the heartbeat is fresh.
|
|
- `1`: daemon is down or the heartbeat is stale.
|
|
|
|
### `tui` / `enodia-sentinel-tui`
|
|
|
|
```bash
|
|
enodia-sentinel tui
|
|
enodia-sentinel-tui
|
|
```
|
|
|
|
Opens the stdlib curses terminal dashboard for SSH/tmux operators. It does not
|
|
require the optional tray dependencies and reads the same local state as the CLI:
|
|
daemon status, recent alert snapshots, incident index and timelines, posture
|
|
findings, integrity/watchdog state, event-rule metadata, event tail, and
|
|
read-only response-plan previews.
|
|
|
|
Keys:
|
|
|
|
- `1` / `2` / `3` / `4`: status, alerts, incidents, timelines.
|
|
- `5` / `6` / `7` / `8` / `9`: posture, integrity, rules, events, response plans.
|
|
- `j` / `k` or arrows: scroll.
|
|
- `r`: refresh.
|
|
- `/`: filter the current view.
|
|
- `:`: command mode.
|
|
- `Tab`: complete command names in command mode.
|
|
- `Ctrl-L`: redraw.
|
|
- `q`: quit.
|
|
|
|
Command mode supports `status`, `alerts`, `incidents`, `timeline`, `posture`,
|
|
`integrity`, `rules`, `events`, `response`, `help`, `refresh`,
|
|
`filter <text>`, `clear`, and `quit`.
|
|
|
|
### `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.
|
|
HTTPS is mandatory. If `web_tls_cert` / `web_tls_key` are not configured,
|
|
Sentinel creates a self-signed pair under `log_dir`; add a browser exception for
|
|
that certificate or replace it with a private/local CA certificate.
|
|
|
|
The console exposes incidents, alert snapshots, posture findings,
|
|
integrity/watchdog state, event-rule metadata, event tail, and dry-run response
|
|
plans. It remains read-only; response commands are displayed for review but not
|
|
executed from the browser. Dashboard settings are local to the browser and
|
|
include persistent themes: Console, Paper, Contrast, LGBTQ, Trans, Dracula,
|
|
Solarized Dark, Solarized Light, and Twilight. The theme registry and core
|
|
text/status contrast pairs are covered by the web test suite.
|
|
|
|
Expected use: `enodia-sentinel-web.service`.
|
|
|
|
### `enodia-sentinel-tray`
|
|
|
|
```bash
|
|
enodia-sentinel-tray
|
|
```
|
|
|
|
Launches the optional desktop system-tray applet (no subcommands). It is a GUI
|
|
frontend over the commands above: the menu opens the dashboard (starting
|
|
`enodia-sentinel-web.service` if needed), starts/stops/restarts
|
|
`enodia-sentinel.service` via `systemctl`, shows daemon status and alert counts
|
|
from `status --json`, and runs `check --json` on demand with the result shown
|
|
as a desktop notification. Requires the `[tray]` extra (`pystray`, `Pillow`);
|
|
the core agent stays dependency-free without it.
|
|
|
|
### `completion`
|
|
|
|
```bash
|
|
enodia-sentinel completion bash
|
|
enodia-sentinel completion zsh
|
|
```
|
|
|
|
Prints a shell completion script for the CLI. Example installs:
|
|
|
|
```bash
|
|
mkdir -p ~/.local/share/bash-completion/completions
|
|
enodia-sentinel completion bash > ~/.local/share/bash-completion/completions/enodia-sentinel
|
|
|
|
mkdir -p ~/.zfunc
|
|
enodia-sentinel completion zsh > ~/.zfunc/_enodia-sentinel
|
|
```
|
|
|
|
For zsh, ensure `~/.zfunc` is in `fpath` before `compinit`.
|
|
|
|
### `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. Suggestions are TOML snippets and
|
|
include the source alert `sid` and `classtype` in comments for review/audit
|
|
context.
|
|
|
|
### `watchdog`
|
|
|
|
```bash
|
|
enodia-sentinel watchdog --url https://100.x.y.z:8787 --token <token> --max-age 120
|
|
enodia-sentinel watchdog --url https://100.x.y.z:8787 --token <token> --insecure-tls
|
|
```
|
|
|
|
Polls a remote dashboard and alerts if the sensor is down, unreachable, or has a
|
|
stale heartbeat. Run this from a separate host. Use `--insecure-tls` only for
|
|
the built-in self-signed certificate while you have no local/private CA trust
|
|
path. 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 respond apply <plan-id>
|
|
```
|
|
|
|
State-changing response commands should be introduced with stable JSON schemas,
|
|
audit logs, and tests before they are documented as supported.
|