HARTWELL_LABS

Talus β€” Architecture

Internal architecture of Talus: the kernel-side eBPF programs, the userspace event pipeline, the sliding-window alerting heuristic,

Talus β€” Architecture

Internal architecture of Talus: the kernel-side eBPF programs, the userspace event pipeline, the sliding-window alerting heuristic, and the output layer.


1. System overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                              KERNEL SPACE                                    β”‚
β”‚   syscall entry           eBPF tracepoint programs            map            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚ execve    │──►│ process-monitor-ebpf             │──►│   EVENTS     β”‚    β”‚
β”‚  β”‚ openat    β”‚   β”‚  #[tracepoint] sys_enter_execve  β”‚   β”‚ PerfEventArrayβ”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚  #[tracepoint] sys_enter_openat  β”‚   β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β”‚ per-CPU    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                             USERSPACE                                        β”‚
β”‚   reader thread ──► MPSC channel ──► Monitor (sliding window + alerting)     β”‚
β”‚                                            β”‚                                β”‚
β”‚                            TUI β”‚ JSON β”‚ Plain β”‚ Diagnose                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Two crates form the workspace:

CrateRoleToolchain
process-monitor-ebpfKernel-side tracepoint programs, #![no_std], aya-ebpfRust nightly (-Z build-std)
process-monitorUserspace: loader, reader thread, monitor core, output modesRust stable

2. Kernel side β€” process-monitor-ebpf

Both programs run in tracepoint context on syscall entry, before the kernel copies arguments, so all userspace pointers are read with bpf_probe_read_user β€” never dereferenced. This keeps the code verifier-safe.

The kernel and userspace agree on a fixed #[repr(C)] layout so records are memcpy'd across the perf buffer without serialization:

pub struct ProcessEvent {
    pub event_type: u8,             // 0 = EXEC, 1 = OPEN
    pub pid: u32,
    pub uid: u32,
    pub comm: [u8; 16],             // process comm (truncated)
    pub filename: [u8; 64],         // target path (truncated)
}

An 85-byte payload that occupies 92 bytes on the wire β€” small and fixed-size, which makes per-CPU perf buffering cheap (no allocation, no variable-length encoding in kernel context).

3. Userspace β€” process-monitor

Startup sequence (Monitor::start)

  1. Privilege check β€” bails unless geteuid() == 0.
  2. Object load β€” aya::Ebpf::load_file parses the compiled eBPF object.
  3. Program load + attach β€” each TracePoint program attaches to syscalls/sys_enter_execve / sys_enter_openat.
  4. Map hand-off β€” the EVENTS PerfEventArray moves into the reader thread.
  5. Channel β€” MPSC connects reader thread β†’ monitor.

Reader thread (talus-reader)

  • Enumerates online CPUs, opens one PerfEventArrayBuffer per CPU.
  • Decodes batches into pre-allocated BytesMut pools (zero per-event allocation in the hot loop).
  • Counts events.lost (perf-buffer overruns) and forwards Msg::Lost.
  • Idles 1 ms when no buffer has data β€” ~1 ms latency, near-zero idle CPU.

Monitor core

stats:   HashMap<u32, ProcStats>          // pid β†’ cumulative stats
windows: HashMap<u32, VecDeque<Instant>>  // pid β†’ open timestamps (1 s window)

handle_event records stats, pushes Open timestamps onto the PID's sliding window, evicts entries older than 1 s, and emits an Alert exactly when the window crosses the configured threshold (--alert-threshold, 0 disables).

Output layer

Monitor::poll returns a Vec&lt;Output&gt; per tick (Event | Alert) routed by mode: TUI (frankentui: 7-panel cyberpunk interface β€” events, process tree, network, top files, extensions, alerts, heatmap) Β· JSON (NDJSON) Β· Plain Β· Diagnose (verifies tracepoint IDs under /sys/kernel/tracing/events, loads + attaches, listens 5 s, prints counters).

4. Data flow summary

kernel                 userspace reader            monitor core            output
──────────             ─────────────────          ─────────────            ──────
openat entry ──► EVENTS map ──► perf buffer ──► Msg::Event ──► sliding window ──► TUI / JSON / plain
                                 (per CPU)            β”‚                β”‚
                                                      └─ Msg::Lost ────► lost counter ──► status bar
                                                                        └─ Alert (threshold) ──► alerts panel

5. Performance characteristics

AspectDesign
Kernel overheadTwo tracepoint programs; fixed-size record; no allocation
Userspace decodePre-allocated BytesMut pools; zero per-event allocation
LatencyEvents typically visible in < 1 ms
Idle CPUReader sleeps 1 ms when no buffers have data
MemorySliding window evicts every poll; maps bounded by live PIDs
BinaryFull LTO + strip = "symbols" + panic = "abort" release profile

6. Licensing subsystem

The commercial licensing layer spans four components (full details in the repo's ARCHITECTURE.md Β§8 and SECURITY.md):

talus-keygen issue ──► signed key (Ed25519) ──► customer
                                                β”‚
                                      talus license activate <KEY>
                                                β–Ό
            Cloudflare Worker + D1 (free tier) ── signature check,
            expiry, revocation, seat limits ──► activation token
ComponentTrust anchor
talus-keygen (owner machine)Private key at ~/.secrets/talus/license-keys/ β€” never inside any repository
Talus binary (license.rs)Embedded Ed25519 public key; compile-time XOR checksum detects binary tampering
Activation server (license-server/)Public key only; signature, expiry, revocation and seat checks in D1; rate limiting 5/5min per machine
Local cacheXOR-obfuscated (0600), re-verified against the signed key on every load β€” local edits to tier/expiry/features fail closed

Documented limit: an attacker who fully controls the binary can patch out license checks β€” signed keys protect the vendor's distribution channel, not a modified client (see the repo SECURITY.md for the full trust model and the key-rotation runbook).

On this page