318 lines
16 KiB
Markdown
318 lines
16 KiB
Markdown
# Enodia Sentinel Roadmap
|
||
|
||
This roadmap moves Enodia Sentinel from a single-host IDS/IPS/EDR agent into a
|
||
fuller host security platform: detect, prevent, 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. |
|
||
| Prevention | Safe containment and recovery actions that start as dry-run plans and require explicit review before any state change. |
|
||
| 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 (process-lineage first,
|
||
time-window fallback) while preserving the existing alert JSON — `incident_id`
|
||
is added at snapshot-report level.
|
||
- ✅ Add an incident timeline generator: `incident show` builds a time-ordered
|
||
view across an incident's member snapshots (time, severity, signatures, PIDs).
|
||
Richer sources (package transactions, dedicated FIM-diff lines) can follow.
|
||
- ✅ Add `enodia-sentinel incident list/show/export`.
|
||
- ✅ Add host posture checks (`enodia-sentinel posture check`):
|
||
SSH root/password/empty-password login, passwordless and writable sudoers,
|
||
world-writable PATH components, loose sensitive-file permissions, disabled
|
||
package signatures, and enabled systemd units with writable unit files or
|
||
`ExecStart` binaries in writable/unsafe paths.
|
||
- ✅ 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` (`sentinel-redteam --list`), with a PASS/MISS verification pass.
|
||
- ✅ Document incident response runbooks for reverse shells, persistence, trojaned
|
||
binaries, hidden listeners, and sensor tampering ([RUNBOOKS.md](RUNBOOKS.md)).
|
||
- ✅ Add Gonzalo/Peopleswar-style behavior coverage: input-device keylogging,
|
||
credential-store/private-key access, SCTP/DCCP/raw/packet-family traffic, and
|
||
raw ICMP/special-protocol rootcheck detection.
|
||
- ✅ Add process-hiding and memory-obfuscation coverage: `/proc` vs `ps`
|
||
cross-view checks plus memory-map scans for hide libraries, RWX memory, and
|
||
executable anonymous/memfd/deleted mappings, plus writable-path executable
|
||
library mappings that suggest userland process injection.
|
||
- ✅ Add event-driven memory telemetry for short-lived `mprotect`/`mmap` RWX,
|
||
`memfd_create`, `ptrace`, seccomp, cross-process memory access, and memory
|
||
locking syscalls.
|
||
|
||
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>`.
|
||
- ✅ Persist CLI-generated dry-run plans under `response-plans/` and append
|
||
`response-audit.log` records for review/handoff.
|
||
- ✅ Add dry-run first actions for evidence freeze, process freeze/terminate,
|
||
outbound IP block, systemd stop/disable, suspicious-file quarantine,
|
||
package-owner lookup, and follow-up verification.
|
||
- ✅ Add package reinstall/restore planning for pacman-owned file findings:
|
||
identify the owning package, verify signed package-cache metadata, reinstall
|
||
from trusted package media, re-run integrity checks, and re-anchor FIM only
|
||
after operator review. Debian/RPM support remains future work.
|
||
- ✅ Add an explicit apply boundary: `respond apply <plan-ref> --dry-run`
|
||
reloads a saved reviewed plan, prints the exact actions, and appends a
|
||
`response_apply_rehearsed` audit record without executing commands.
|
||
- Require a future explicit execution flag for state changes; default to
|
||
read-only plans and audited apply rehearsals.
|
||
- ✅ Extend response audit logs to the apply boundary with `executed: false`
|
||
records. Future state-changing execution must append the same audit trail.
|
||
- ✅ Add baseline reconciliation: accept legitimate FIM/package/listener/SUID
|
||
drift with a mandatory reason via `baseline accept/revoke/list`. Acks are
|
||
fingerprint-bound (content kinds re-alert on further change), support an
|
||
optional TTL, and are filtered at the daemon chokepoint, `fim-check`, and the
|
||
dashboard integrity API.
|
||
- ✅ 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.
|
||
- ✅ Add a local dashboard Settings menu with persistent themes for dark
|
||
console, light evidence review, high-contrast operation, LGBTQ, Trans,
|
||
Dracula, Solarized Dark/Light, and Twilight palettes.
|
||
- ✅ Add dashboard theme contrast/visual smoke checks so readable text and
|
||
meaningful status colors are preserved across every palette.
|
||
- ✅ Add an optional desktop tray applet (`enodia-sentinel-tray`, `[tray]`
|
||
extra): dashboard launcher, systemd daemon control, status at a glance, and
|
||
`check --json` quick checks, with GUI-free tested logic modules.
|
||
- 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 the first typed host-event rule family beside exec/syscall rules:
|
||
`rules test` accepts `tcp_connect` event JSON and ships SID `100067` for
|
||
interpreter egress to unusual public ports, with a replayable fixture.
|
||
- ✅ Add a second typed host-event rule family for `listen` events: SID `100068`
|
||
flags interpreters opening listeners on unusual local ports, with local-port
|
||
match conditions and a replayable fixture.
|
||
- ✅ Continue generalizing the rule engine across more typed host events:
|
||
`file_write`, `chmod/chown`, `setuid/setgid`, `bind`, `accept`, `capset`,
|
||
and module-load now have built-in rules, typed event parsing, fixtures, and
|
||
rule-operation tests.
|
||
- Keep rules declarative and Snort-like:
|
||
`sid`, `msg`, `severity`, `classtype`, `event`, and match fields such as
|
||
process name, parent process, argv regex, path prefix, peer IP/port, UID/GID,
|
||
package ownership, and lineage.
|
||
- 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.
|
||
- ✅ Start correlating multi-stage behavior with a read-only incident
|
||
correlation engine: `exec_rule.web-rce` plus suspicious egress or an unusual
|
||
listener in a 10-minute incident window raises SID `100080`
|
||
`correlation.multi_stage_intrusion` while preserving the raw alerts.
|
||
- Continue expanding 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, including:
|
||
- `exec_rule.web-rce` + suspicious egress + persistence within 10 minutes →
|
||
CRITICAL multi-stage intrusion.
|
||
- `new_listener` + `new_suid` in the same lineage/window → CRITICAL backdoor
|
||
with privilege-escalation artifact.
|
||
- `fim_modified` or `pkg_signature_mismatch` + `pkgdb_tamper` → CRITICAL
|
||
suspected trojaned binary with checksum cover-up.
|
||
- web/database service spawning shell + tool transfer (`curl|wget|sh`) →
|
||
CRITICAL likely RCE payload staging.
|
||
- Add additional high-signal living-off-the-land detections:
|
||
`curl|wget | sh`, Python/perl/bash reverse-shell argv variants,
|
||
`systemctl enable` from unusual ancestry, `chmod +s` outside package
|
||
transactions, and execution from writable directories.
|
||
- ✅ Add first-seen and rarity tracking for local network behavior:
|
||
first public destination per process name and first listening port per
|
||
process name are locally baselined in `first-seen.json` and alert after the
|
||
initial silent pass.
|
||
- Continue rarity coverage for:
|
||
interpreter connections to non-standard ports and long-lived low-byte
|
||
connections when socket sampling can support it.
|
||
- 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.
|
||
- Multi-stage correlation raises one higher-confidence incident without hiding
|
||
the underlying raw alerts.
|
||
|
||
## v1.1.1: Detection Quality and Rule Operations
|
||
|
||
Purpose: make detection coverage easier to audit, tune, and extend without
|
||
turning Sentinel into a noisy rules dump.
|
||
|
||
- ✅ Add `enodia-sentinel rules list` and `rules show <sid>` for built-in and
|
||
configured rules.
|
||
- ✅ Add `enodia-sentinel rules test <event-json>` so operators can validate
|
||
custom event rules against captured or fixture events.
|
||
- ✅ Generate rule documentation from source defaults: SID, signature, classtype,
|
||
event type, match fields, expected false positives, and drill coverage.
|
||
- ✅ Require a fixture or safe red-team drill for every built-in SID, including
|
||
event-only and correlation SIDs.
|
||
- ✅ Add compatibility tests for rule JSON/event schema evolution.
|
||
- ✅ Extend triage so false-positive suggestions are tied to SID/classtype and can
|
||
emit TOML snippets for allowlists, correlation windows, or severity overrides.
|
||
- ✅ Add post-alert enrichment consistently across detections:
|
||
package owner, executable hash, parent/ancestor chain, remote IP
|
||
classification, file mode/owner, recent writes by same lineage, and current
|
||
FIM/package/rootcheck status.
|
||
|
||
Exit criteria:
|
||
|
||
- Operators can inspect, test, document, and tune detection rules without
|
||
reading Python source.
|
||
- Every shipped detection rule has reproducible fixture/drill coverage and stable
|
||
operator documentation.
|
||
|
||
## v1.2: Fleet Mode
|
||
|
||
Purpose: support several personal or small-business Linux hosts without turning
|
||
the agent into a heavy enterprise platform.
|
||
|
||
- ✅ Add a fleet collector design note covering optional collector boundaries,
|
||
per-host enrollment, signed JSON payloads, HTTPS/tailnet transport, and
|
||
standalone-agent failure semantics ([FLEET_DESIGN.md](FLEET_DESIGN.md)).
|
||
- 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 locally with append-only
|
||
`enodia.hash_chain.v1` JSONL records, exposed through the integrity report.
|
||
- 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.
|
||
|
||
## Active Migration Track
|
||
|
||
- Suricata model assimilation design is complete, and Phase 0's additive
|
||
`enodia.event.v1` Python reference contract is implemented.
|
||
- Phase 1 under `go-agent/` now has a stdlib-only parallel sweep loop,
|
||
injectable process/file-descriptor/socket state, and all current Python poll
|
||
detectors ported: `reverse_shell`, `ld_preload`, `deleted_exe`,
|
||
`input_snooper`, `credential_access`, `stealth_network`, `egress`,
|
||
`memory_obfuscation`, `first_seen`, `new_listener`, `persistence`, and
|
||
`new_suid`. The shared fixture and `scripts/check-go-parity.py` check Go
|
||
output against the Python oracle. A new `provenance` package provides
|
||
pacman/dpkg/rpm ownership queries so the Go `new_listener` detector can
|
||
mirror Python's package-owned-listener suppression. Opt-in `--state-dir`
|
||
mode now builds listener/SUID baselines, applies the shared startup grace
|
||
gate, scans persistence paths, and retains first-seen network state. The Go
|
||
sidecar also has offline-replay parity for the exec rule family (built-ins
|
||
`100001`–`100004` plus configured `[[exec_rules]]`) and syscall rules
|
||
`100060`–`100066`, plus all typed host rules, the active rule catalog,
|
||
mixed-event JSONL routing, correlation SID `100080`, and sweep cooldown. The
|
||
first Phase 2 transports have landed as an opt-in `cilium/ebpf` execve
|
||
tracepoint source plus a filtered raw-syscall source for SIDs `100060`–
|
||
`100066`, both with CO-RE parent lookup, shared cooldown/emission, and
|
||
fail-open probe status. The syscall source also routes `setuid`/`setgid` into
|
||
typed-host SIDs `100071`/`100072` and permission/ownership changes into SID
|
||
`100070`, including relative-path resolution. Confirmed interpreter
|
||
`write`/`writev`/`pwrite` variants,
|
||
successful TCP connect/bind/listen/accept calls, `capset`, and `finit_module`
|
||
now feed typed-host SIDs `100069`, `100067`/`100073`/`100068`/`100076`,
|
||
`100077`, and `100078`. Phase 2 now also includes an opt-in hardened systemd
|
||
validation unit, static build/install path, isolated baseline state, and
|
||
journal JSONL. A bounded rotating JSONL copy provides isolated retained event
|
||
history, and an atomic heartbeat gives external watchdogs a non-journal
|
||
liveness signal. Bounded Python-schema-compatible JSON/text alert snapshots
|
||
now provide truthful retained counts and basic process context. The Python
|
||
service remains the production default until incident grouping, rich
|
||
enrichment/assurance, management-console consumers, and broader subsystem
|
||
parity land.
|
||
|
||
## 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.
|
||
|
||
## 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.
|