Generate event rule documentation

This commit is contained in:
Luna 2026-06-15 18:52:28 -07:00
parent e43d7689a0
commit 7f4d5b42fd
8 changed files with 378 additions and 5 deletions

View file

@ -62,9 +62,9 @@ def main(argv: list[str] | None = None) -> int:
sub.add_parser("baseline", help="rebuild listener/SUID baselines")
sub.add_parser("list-detectors", help="list available detectors")
rules = sub.add_parser("rules",
help="list/show/test built-in and configured rules")
help="list/show/test/docs built-in and configured rules")
rules.add_argument("action", nargs="?", default="list",
choices=["list", "show", "test"],
choices=["list", "show", "test", "docs"],
help="rule action (default: list)")
rules.add_argument("target", nargs="?",
help="sid for show, event JSON path for test, or '-'")
@ -170,6 +170,10 @@ def _cmd_rules(cfg: Config, action: str, target: str | None,
from . import ruleops
if action == "docs":
print(ruleops.render_markdown(cfg), end="")
return 0
if action == "list":
rules = ruleops.list_rules(cfg)
if as_json:

View file

@ -40,6 +40,51 @@ def find_rule(cfg: Config, sid: int) -> dict[str, Any] | None:
return None
def render_markdown(cfg: Config) -> str:
"""Render active event-rule metadata as operator documentation."""
lines = [
"# Enodia Sentinel Event Rule Reference",
"",
"Generated from the active exec/syscall rule defaults plus any configured "
"`exec_rules_file` entries.",
"",
"Use `enodia-sentinel rules list/show/test` to inspect rules and validate "
"event fixtures locally.",
"",
]
for rule in list_rules(cfg):
doc = _RULE_DOCS.get(rule["sid"], {})
lines.extend([
f"## SID {rule['sid']}: {rule['msg']}",
"",
f"- Event: `{rule['event']}`",
f"- Signature: `{rule['signature']}`",
f"- Classtype: `{rule['classtype']}`",
f"- Severity: `{rule['severity']}`",
f"- Origin: `{rule['origin']}`",
"",
"Match fields:",
])
conditions = rule.get("conditions", {})
if conditions:
for key, value in conditions.items():
lines.append(f"- `{key}`: {_format_condition(value)}")
else:
lines.append("- none")
lines.extend([
"",
"Expected false positives:",
])
for fp in doc.get("false_positives", _default_false_positives(rule)):
lines.append(f"- {fp}")
lines.extend([
"",
f"Drill or fixture: {doc.get('drill', _default_drill(rule))}",
"",
])
return "\n".join(lines).rstrip() + "\n"
def load_event_json(path: str) -> dict[str, Any]:
if path == "-":
import sys
@ -96,6 +141,105 @@ def _syscall_rule_record(rule: SyscallRule, origin: str) -> dict[str, Any]:
}
_RULE_DOCS: dict[int, dict[str, Any]] = {
100001: {
"false_positives": [
"Temporary build or installer helpers intentionally executed from `/tmp`, `/var/tmp`, or `/dev/shm`.",
"One-shot administrative diagnostics copied into a writable directory.",
],
"drill": "`sentinel-redteam ebpf_exec` fires a short-lived writable-path execution event when the eBPF exec monitor is enabled.",
},
100002: {
"false_positives": [
"Security training labs or safe self-tests that intentionally include reverse-shell command text.",
"Benign scripts containing literal `/dev/tcp` or `pty.spawn` examples in their argv.",
],
"drill": "`sentinel-redteam ebpf_exec` emits a `/dev/tcp` argv fixture; `rules test` can validate a captured exec JSON event offline.",
},
100003: {
"false_positives": [
"Legitimate web applications invoking maintenance scripts through shell wrappers.",
"Database or web service containers whose entrypoint intentionally spawns an interpreter.",
],
"drill": "Use `rules test` with an exec event whose `parent_comm` is a web/database service and whose `filename` is an interpreter.",
},
100004: {
"false_positives": [
"Bootstrap scripts that intentionally pipe downloaded install content into a shell.",
"Developer setup tooling run interactively during provisioning.",
],
"drill": "Use `rules test` with argv like `curl http://example.invalid/x | sh`.",
},
100060: {
"false_positives": [
"JIT runtimes or emulators that make pages writable and executable.",
"Security tooling that deliberately tests W^X policy.",
],
"drill": "Use `rules test` with syscall `mprotect` and `args[2]` containing write+exec permissions, for example `0x6`.",
},
100061: {
"false_positives": [
"JIT runtimes, emulators, or language VMs that allocate RWX memory.",
"Compatibility layers that request executable writable mappings.",
],
"drill": "Use `rules test` with syscall `mmap` and `args[2]` containing write+exec permissions.",
},
100062: {
"false_positives": [
"Browsers, sandbox helpers, and runtimes that legitimately use anonymous memfd files.",
"Container or IPC frameworks using memfd as a transport primitive.",
],
"drill": "Use `rules test` with syscall `memfd_create`; include `text` to document the captured name.",
},
100063: {
"false_positives": [
"Debuggers, profilers, crash handlers, and endpoint tooling that attach to processes.",
"Developer sessions running `strace`, `gdb`, or similar tracing tools.",
],
"drill": "Use `rules test` with syscall `ptrace` and request `16` (`PTRACE_ATTACH`) or `0x4206` (`PTRACE_SEIZE`).",
},
100064: {
"false_positives": [
"Browsers, container runtimes, and sandboxed services enabling seccomp as normal hardening.",
"Security test harnesses validating seccomp policy.",
],
"drill": "Use `rules test` with syscall `seccomp`, or syscall `prctl` with `arg0` set to `22` (`PR_SET_SECCOMP`).",
},
100065: {
"false_positives": [
"Debuggers, profilers, memory scanners, and EDR tools inspecting another process.",
"Backup or checkpoint tooling that reads process memory intentionally.",
],
"drill": "Use `rules test` with syscall `process_vm_readv` or `process_vm_writev`.",
},
100066: {
"false_positives": [
"Databases, crypto agents, and credential stores locking sensitive memory.",
"Realtime or performance-sensitive services using `mlock` intentionally.",
],
"drill": "Use `rules test` with syscall `mlock`, `mlock2`, or `mlockall`.",
},
}
def _format_condition(value: Any) -> str:
if isinstance(value, list):
return ", ".join(f"`{v}`" for v in value) or "`[]`"
return f"`{value}`"
def _default_false_positives(rule: dict[str, Any]) -> list[str]:
if rule.get("origin") == "configured":
return ["Operator supplied rule; document expected benign matches beside the TOML rule."]
return ["No documented benign case yet; validate with `rules test` before tuning."]
def _default_drill(rule: dict[str, Any]) -> str:
if rule.get("origin") == "configured":
return "Use `enodia-sentinel rules test <event-json>` with a fixture that should match the configured rule."
return "Use `enodia-sentinel rules test <event-json>` with a representative fixture."
def _event_kind(event: dict[str, Any]) -> str:
explicit = str(event.get("event") or event.get("type") or "").lower()
if explicit in {"exec", "execve"}: