# 3. Event Detection Scope: the transport-independent contract for exec and syscall events and the rules that turn them into alerts. Python remains the oracle. The Go port accepts replay fixtures and reproduces the alert envelopes. Its opt-in native exec and syscall sources now feed the same contract through `cilium/ebpf`; typed-host kernel transports remain a separate boundary. Typed host-event rules are part of the same replay and catalog contract. ## 3.1 Exec events `ExecEvent` fields are `pid`, `ppid`, `uid`, `parent_comm`, `filename`, and `argv`. `argv` excludes `filename`. - `exec_comm` is the POSIX basename of `filename`. A trailing slash therefore produces an empty basename; ports must not clean the path first. - `argv_str` is `filename` and every captured argv item joined with one space, then stripped at both ends. - Rules AND every condition they specify. Values within one condition are ORed. - A rule with no positive condition is invalid. `parent_exclude` is a veto, not a positive condition. - `argv_regex` is case-insensitive and searches the whole `argv_str`. Alert contract: | Field | Value | |---|---| | signature | `exec_rule.` | | key | `exec::` | | pids | the event PID, even when zero | | detail | `sid= — pid=… ppid=… parent=… exec=… argv=[…]` | The argv text in detail is truncated to 120 characters. Built-ins: | SID | Severity | Classtype | Match | |---|---|---|---| | 100001 | CRITICAL | `fileless-execution` | filename begins `/tmp/`, `/dev/shm/`, or `/var/tmp/` | | 100002 | CRITICAL | `c2-reverse-shell` | reverse-shell regex in `argv_str` | | 100003 | CRITICAL | `web-rce` | web/DB parent launches a configured interpreter basename | | 100004 | HIGH | `ingress-tool-transfer` | curl/wget/fetch piped to a shell | Configured rules are appended after built-ins from `[[exec_rules]]` tables in `exec_rules_file`. Missing files mean built-ins only. Unknown severity strings fall back to HIGH, matching Python. ## 3.2 Syscall events `SyscallEvent` fields are `pid`, `ppid`, `uid`, `comm`, `syscall`, six unsigned arguments (`arg0`…`arg5`), and optional `text`. Missing arguments are zero. Alert contract: | Field | Value | |---|---| | signature | `syscall_rule.` | | key | `syscall:::::` | | pids | the event PID | | detail | rule metadata plus `arg0`…`arg3` formatted as prefixed lowercase hex | Optional text is appended as ` text=[…]` and truncated to 80 characters. | SID | Severity | Classtype | Match | |---|---|---|---| | 100060 | CRITICAL | `memory-obfuscation` | `mprotect`, arg2 contains both WRITE (`0x2`) and EXEC (`0x4`) | | 100061 | CRITICAL | `memory-obfuscation` | `mmap`, arg2 contains WRITE and EXEC | | 100062 | MEDIUM | `fileless-execution` | any `memfd_create` | | 100063 | HIGH | `anti-analysis` | `ptrace` request TRACEME (`0`), ATTACH (`16`), or SEIZE (`0x4206`) | | 100064 | MEDIUM | `anti-analysis` | any `seccomp`, or `prctl` arg0 `PR_SET_SECCOMP` (`22`) | | 100065 | HIGH | `credential-access` | `process_vm_readv` or `process_vm_writev` | | 100066 | MEDIUM | `memory-obfuscation` | `mlock`, `mlock2`, or `mlockall` | ## 3.3 Current Go acceptance boundary `scripts/check-go-parity.py` replays shared JSON into both implementations and compares decoded `enodia.event.v1` alert envelopes exactly. Acceptance requires: - all four built-in exec rules plus one configured rule; - all seven syscall rules and all ten host rules plus negative predicate cases; - identical SID, severity, signature, classtype, key, detail, PID list, host, and timestamp; - no BPF/root/kernel dependency for replay mode. Replay flags are `--exec-events`, `--syscall-events`, and `--host-events`. They emit alert JSONL only and exit. They remain deterministic verification surfaces independent of the live source. `--ebpf-exec` loads a compiled tracepoint program with `cilium/ebpf`, captures PID, parent PID, UID, caller comm, filename, and the first two arguments, then passes each record through the same exec rule engine, shared cooldown gate, and `enodia.event.v1` builder. Parent lookup uses CO-RE relocation against kernel BTF. Startup and reader failures update `ebpf`/`ebpf_exec` status and leave the poll loop running. The flag is opt-in and requires Go 1.25+ to build. `--ebpf-syscall` attaches one raw-syscall entry tracepoint. An architecture- specific map allows only `mprotect`, `mmap`, `memfd_create`, `ptrace`, `prctl`, `seccomp`, `process_vm_readv`, `process_vm_writev`, `mlock`, `mlock2`, and `mlockall` through the BPF program, plus `setuid` and `setgid`, avoiding a user-space event for every host syscall. It captures the six arguments and the memfd name before routing syscall events through SIDs `100060`–`100066`. `setuid`/`setgid` become typed host events for SIDs `100071`/`100072`. The portable `fchmodat`/`fchownat` calls, plus legacy `chmod`/`chown`/`lchown` on amd64, become `chmod`/`chown` host events for SID `100070`. Relative paths are resolved against `/proc//cwd` before matching persistence prefixes. The `capset` effective bitmap is decoded to canonical capability names for SID `100077`. `finit_module` resolves its file descriptor through `/proc//fd` and supplies path/module fields to SID `100078`. Syscall numbers come from build-target `golang.org/x/sys/unix` constants rather than a hand-maintained architecture table. Static builds are exercised for `amd64`, `arm64`, `riscv64`, and big-endian `s390x`. ## 3.4 Typed host events `HostEvent` covers `tcp_connect`, `listen`, `bind`, `accept`, `file_write`, `chmod`, `chown`, `setuid`, `setgid`, `capset`, and `module_load`. Compatibility aliases accepted by `ruleops` are stable: `type`, `remote_*`, `bind_*`, `listen_*`, `uid_target`, `gid_target`, `caps`, `cap_effective`, singular `capability`, and module `name`. Numeric strings accept decimal/hex/octal; absent target UID/GID values are `-1`, not zero. Host alerts use signature `host_rule.` and a key containing every typed identity field. Capabilities are normalized uppercase for matching and rendering. Built-in SIDs are `100067`–`100073` and `100076`–`100078`. ## 3.5 Mixed pipeline and catalog `--event-stream` accepts mixed JSONL (or `-` for stdin), infers exec/syscall events when no explicit type exists, and routes typed events through one transport-independent rule set. `--rules-list` and `--rules-show` expose the same sorted metadata and conditions as Python `ruleops`. The native exec/syscall readers and all replay modes feed this boundary. The syscall reader also supplies typed-host transports for root UID/GID transition requests, persistence-path permission/ownership changes, and capability-set requests. `finit_module` supplies module-load events with resolved paths. Interpreter `write`, `writev`, `pwrite64`, `pwritev`, and `pwritev2` calls are filtered by the same comm list as SID `100069`, correlated across raw syscall enter/exit in a bounded BPF map, emitted only for positive byte counts, and resolved through `/proc//fd`. Successful IPv4/IPv6 `connect` and `bind` calls use the same enter/exit correlation and in-kernel interpreter filter for SIDs `100067`/`100073`. Connect events are verified against `/proc/net/tcp{,6}` by socket inode so UDP cannot be mislabeled as `tcp_connect`. Successful `listen`, `accept`, and `accept4` calls resolve their socket FD to local/peer TCP endpoints through the same inode tables for SIDs `100068`/`100076`. Every current typed-host event family now has a native transport.