docs · architecture
Architecture.
oknek is a single static Go binary (oknekd, ~4 MB) plus a CLI (oknek, ~2 MB) on every host. It hooks AI-agent processes at the kernel layer, runs every observed action through a YAML rule engine, writes signed evidence bundles to a local SQLite store, and renders a verdict (allow / warn / block) before the originating syscall returns. The whole pipeline is sub-3ms end-to-end on modern Linux.
the five-stage funnel
From agent action to verdict.
stage 02 in detail
The hook layer.
eBPF (primary). On Linux kernel ≥ 5.8 with BTF (CO-RE) support, oknekd loads a small eBPF program at install time. It attaches to execve, execveat, open, openat, connect, sendmsg, and inotify_add_watch via fentry / kprobe / tracepoint depending on the syscall. The probe is non-blocking by default — it queues the event to a ringbuffer that oknekd drains in userspace. For rules with action: block, the probe uses a signal injection technique to SIGSTOP the originating thread until oknekd writes a verdict back.
LD_PRELOAD (fallback). On older kernels (4.x–5.7) or where eBPF is unavailable, oknekd drops /usr/local/lib/oknek_preload.so. Systemd-launched AI-agent services have LD_PRELOAD=/usr/local/lib/oknek_preload.so added to their environment via a systemd drop-in. The shim intercepts the libc layer (execve, open, socket) and forwards events to /run/oknek/oknek.sock. Same event schema as eBPF; same rule engine consumes it. Note: LD_PRELOAD is bypassable by static binaries and by agents that scrub LD_* environment variables — eBPF is the durable hook. We strongly recommend kernel 5.8+ in production.
DYLD_INSERT_LIBRARIES (macOS dev only). Mirror of LD_PRELOAD using dyld. Same shim binary, recompiled. Intended for developer machines running Claude Code locally — not for production servers.
stage 03 in detail
The rule engine.
Rules are YAML documents loaded from /var/lib/oknek/rules/active/. On daemon start, oknekd parses every rule, compiles each match predicate into a closure, and indexes rules by kind (which kernel hook produces the event). The result is a per-kind dispatcher: when an execve event arrives, only rules with kind: exec_observed evaluate against it. This keeps the hot path under ~150 microseconds even with hundreds of rules loaded.
Rule pack reloads (e.g., from oknek update) swap the dispatcher atomically using copy-on-write. No event is dropped during a reload; the daemon serves the old dispatcher until the new one is ready, then flips a pointer. Typical reload time: 10–20 ms.
See /docs/rules/ for the full schema.
stage 04 in detail
Evidence + storage.
Every rule fire — allow, warn, or block — produces an evidence bundle: a structured JSONL record with the rule ID, agent identifier, captured fields (defined by the rule's evidence.capture), kernel-level metadata (pid, ppid, uid, cgroup, namespace), and an Ed25519 signature over the bundle. The signature uses a per-host key generated at install time and re-derived from /var/lib/oknek/host.key. Tampering with the local DB is detectable.
The local SQLite database lives at /var/lib/oknek/oknek.db. It contains three primary tables: events (one row per rule fire), baseline (the rolling 14-day per-agent feature counts used by R7), and meta (schema version, install ID, host key fingerprint). journal_mode = WAL, synchronous = NORMAL. The daemon never reads or writes anywhere outside its own dirs.
On Pro tier and above, evidence bundles are also exported in NDJSON to a configured webhook URL (Slack, Discord, PagerDuty, generic). Forensic replay (Business+) reconstructs the full agent decision tree by replaying the recorded events plus the captured tool-call argv strings.
stage 05 + what happens after
Verdict.
Three terminal states:
- allow — the rule matched but its action is
allow, or no rule matched. Event recorded for the baseline; agent continues. - warn — alert fires (Slack/Discord/email/webhook); event recorded; agent continues. Useful for tuning before turning a rule to
block. - block — agent process suspended via SIGSTOP. Operator confirms via
oknek allow <pid>oroknek block <pid>(permanent). Pending syscall returnsEPERMorEACCESdepending on the syscall.
End-to-end latency from syscall entry to verdict written: ~3 ms median, ~12 ms p99 on Ubuntu 24.04 / kernel 6.8 / amd64. The verdict is enforced before the syscall returns to userspace, which means a blocked open() never gives the agent a usable file descriptor.
threat model assumptions
What we assume.
oknek's defenses hold under the following assumptions. Outside them, all bets are off.
- The kernel is trusted. A kernel rootkit can bypass any eBPF / LD_PRELOAD hook. If your kernel is compromised, oknek is also compromised. Defend the kernel separately.
- The oknekd binary is intact. The install script verifies the Ed25519 signature on the binary. After install, file integrity (AIDE / auditd) is your job. We don't watch ourselves; that's a separate AIDE-shaped problem.
- The host clock is roughly correct. NTP or chronyd should be running. Some rules (notably custom
wall_clock_*predicates) depend on it. - The agent runs as a normal user, not as root. If your agent runs as root, it can do anything root can do — including unloading the eBPF program. Running AI agents as root is itself a security issue oknek can't solve. Drop privileges first.
- The host has a functioning systemd. oknekd is a systemd-managed service. Non-systemd init systems are not yet supported.
That's the architecture.
Now see what we catch with it, or request access for a scoped pilot on your own box.