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.

01 · agent action execve · open · socket · connect
02 · kernel hook eBPF (≥5.8) · LD_PRELOAD fallback
03 · rule engine R1–R7 · YAML · hot-reload
04 · evidence signed JSONL + SQLite
05 · verdict allow · warn · block

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> or oknek block <pid> (permanent). Pending syscall returns EPERM or EACCES depending 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.