enodia-sentinal/docs/COMMAND_REFERENCE.md
Luna dbbe35b3fc Add baseline reconciliation: accept audited drift with a reason
Implements the v0.9 roadmap item from the approved design spec. Operators
accept a specific FIM/package/listener/SUID drift item with a mandatory
reason; the ack suppresses that one alert only while the live state still
matches the recorded fingerprint. Content kinds (fim/pkgfile) re-alert on
further change; identity kinds (listener/suid) retire on TTL or revoke.

- reconcile.py: ReconcileStore (mtime-cached, fails closed on missing/corrupt
  store), fingerprint builders, and the filter_alerts chokepoint.
- CLI: baseline accept/revoke/list with --reason/--expires/--force/--stale/--json.
- Wired at the daemon sweep + eBPF chokepoint, fim-check, and /api/integrity.
- RECONCILE_V1 schema, config knob, COMMAND_REFERENCE/SCHEMAS/OPERATIONS/ROADMAP
  docs, and reconcile unit/CLI/integration tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 10:33:55 -07:00

435 lines
14 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
```
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.
### `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 lookup, and follow-up verification checks.
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.
### `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`.
### `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 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.