Documents
ADR-0001 - Adopt Rust for DaemonEye Engine
ADR-0001 - Adopt Rust for DaemonEye Engine
Type
External
Status
Published
Created
Apr 18, 2026
Updated
Apr 20, 2026
Updated by
Dosu Bot
Source
View

Status#

Accepted (reaffirmed 2026-04-16)

Supersedes the database part of the original decision. The original ADR selected SQLite; the project has since migrated to redb (pure Rust embedded KV store). See Decision → Database below. Language selection (Rust) is unchanged.

Context#

The existing ProcMonD prototype was implemented in Python with SQLite as the database backend. While the Python implementation proved the concept, it faced several limitations in production environments:

  • Performance bottlenecks. Process enumeration and hash calculation are CPU-intensive operations that suffer from Python's Global Interpreter Lock (GIL).
  • Memory consumption. Python's object overhead and garbage collection patterns lead to higher memory usage than necessary.
  • Deployment complexity. Requires Python runtime, virtual environments, and dependency management in production.
  • Cross-platform challenges. Platform-specific imports and dependencies require complex fallback mechanisms.
  • Security concerns. Dynamic typing and runtime interpretation increase attack surface compared to compiled alternatives.

Business requirements (purposes)#

These are the functional purposes that drive the language decision. Each is evaluated by what it needs to accomplish, not by which library implements it:

#PurposeConstraint
1Detect process anomalies without degrading host performance<5% sustained CPU, <100MB RAM, <5s for 10k+ processes
2Prevent injection in the custom SQL-like detection DSLAST validation at load time, SELECT-only enforcement, function whitelist
3Maintain tamper-evident forensic audit trailAppend-only ledger, BLAKE3 hash chains, Merkle tree inclusion proofs
4Enforce privilege separation with immediate dropOnly procmond elevates; detection/alerting in user space
5Operate offline-first in air-gapped environmentsEmbedded database, no external dependencies, bundle-based config
6Collect kernel-level events on Linux, Windows, and macOSeBPF (Linux), ETW (Windows), EndpointSecurity (macOS)
7Scrub cryptographic secrets from memory after useDeterministic zeroization, no secret lingering in RAM
8Deploy as single binary to all target platformsLinux, macOS, Windows, FreeBSD on x86_64 and ARM64
9Satisfy CISA/NSA memory-safe language guidanceSTIG/CMMC/IC compliance narrative
10Deliver open-source Rust crate connectorspowerbi-rs, qradar-rs, logrhythm-rs for ecosystem contribution

Decision#

Language: Rust#

Implement DaemonEye using Rust as the primary language.
Rationale by purpose:

  • Memory safety (Purposes 7, 9). Compile-time ownership model eliminates buffer overflows, use-after-free, and data races. unsafe_code = "forbid" is enforceable at workspace level — no equivalent enforcement exists in Go.
  • Deterministic resource cleanup (Purpose 7). Rust's drop model guarantees secrets are scrubbed when values go out of scope. zeroize + secrecy crates provide cross-platform deterministic zeroization. Go's GC cannot guarantee when or whether memory is reclaimed, and runtime/secret (Go 1.26) is experimental and Linux-only.
  • Predictable performance (Purpose 1). No garbage collector means no GC pauses competing with the <5% CPU budget. Under a tight <100MB RSS cap, Go's GC tuning (GOGC + GOMEMLIMIT) trades CPU for headroom — an inherent tension that Rust avoids.
  • Cross-platform kernel collection (Purpose 6). Rust's FFI via bindgen (no runtime overhead) is cleaner than CGO for wrapping platform-native APIs (ETW on Windows, EndpointSecurity on macOS). Windows gap is narrower than initially assessed; see Alternatives Considered → Go.
  • Single binary deployment (Purpose 8). Both Rust and Go produce static binaries for pure code. Platform collectors requiring native bindings use FFI in Rust vs CGO in Go — Rust's approach preserves the static binary while CGO typically does not.
  • Ecosystem contributions (Purpose 10). Commercial roadmap includes open-source Rust crate deliverables (LogRhythm, QRadar, Power BI connectors). These are ecosystem plays specific to the Rust community.

Database: redb (updated from the original SQLite selection)#

The original ADR selected SQLite. The project has since migrated to redb (pure Rust embedded key-value store) for:

  • In-process operation. No external dependencies, suitable for air-gapped deployment.
  • ACID transactions. Full transactional guarantees for audit ledger integrity.
  • Concurrent access. MVCC with multiple concurrent readers, unlike bbolt's single-writer model.
  • Memory efficiency. Zero-copy deserialization keeps RSS within the <100MB budget.
  • Pure Rust. No C dependencies, no linking complexity, auditable codebase.
    See ADR-0002 for current crate selections and ADR-0006 for the Phase 2 SQL execution decision (DataFusion over redb via custom TableProviders).

Technology stack summary#

  • Runtime: Rust 2024 Edition (MSRV 1.91+) with async/await via Tokio.
  • Database: redb (pure Rust embedded KV store).
  • Process enumeration: sysinfo (cross-platform, parallel enumeration).
  • CLI: clap v4 with derive macros.
  • Cryptography: BLAKE3, SHA-256, ed25519-dalek, zeroize, secrecy.
  • Serialization: serde ecosystem, protobuf for IPC.

Migration approach#

  1. Functional parity. Implement all existing detection capabilities before adding enhancements.
  2. Configuration evolution. Migrate from INI to YAML/TOML with conversion utilities.
  3. Incremental validation. Phase-based approach allowing parallel operation during transition.

Consequences#

Positive#

  • Performance gains. Expected 5-10x improvement in CPU efficiency and memory usage vs Python.
  • Security enhancement. Compile-time safety eliminates buffer overflows and memory corruption.
  • Deterministic secret handling. zeroize/secrecy provide cross-platform scrubbing guarantees.
  • Operational simplicity. Single binary deployment with no runtime dependencies.
  • Cross-platform consistency. Uniform behavior across all supported operating systems.
  • Development velocity. Strong typing and comprehensive testing reduce debugging time.
  • Ecosystem contribution. Open-source Rust crate connectors (powerbi-rs, qradar-rs, logrhythm-rs).
  • Compliance. Rust is listed as a memory-safe language in the June 2025 CISA/NSA CSI.

Negative#

  • Learning curve. Team must become proficient in Rust development patterns. Mitigation: dedicated Rust learning resources and gradual team onboarding.
  • Migration effort. Existing Python codebase cannot be directly translated. Mitigation: phase-based approach allowing parallel operation during transition.
  • Ecosystem risk. Some specialized libraries may be less mature than Python equivalents. Mitigation: prototype critical dependencies early to validate maturity.
  • Compilation time. Rust compilation is slower than Python interpretation during development. Mitigation: optimize build pipeline with cargo-watch and incremental compilation.

Neutral#

  • Success metrics. Process enumeration <5 seconds for 10,000+ processes; <100 MB resident memory under normal operation; <5% sustained CPU usage during continuous monitoring; >99.9% uptime target; functional parity with Python prototype within 12 weeks of start.
  • Review triggers. Revisit at v1.0 completion (target 2026-10-09), or if Go's runtime/secret reaches stable with cross-platform support, or if a Linux-first product variant is pursued (hybrid architecture becomes favored), or if team size grows beyond single maintainer (hybrid toolchain cost becomes acceptable).
  • Review history.
    • 2025-01-13 — Accepted (original decision, Rust + SQLite).
    • 2026-04-16 — Reaffirmed (purpose-driven Go feasibility review; DB migrated to redb; Go rejected on Purposes 6 and 7).
    • 2026-04-16 — Deep-dive addendum (ETW consuming proven pure-Go; ESF gap narrowed to one Obj-C block; hybrid architecture documented as future option).
    • 2026-04-18 — Backfilled to Nygard-style template.

References#

Go ecosystem (ETW): microsoft/go-winio (MIT, Microsoft-maintained, no CGO); 0xrawsec/golang-etw (GPL-3, incompatible, but proves the technique); Microsoft ETW docs; Go issues #20823, #9240 on foreign-thread callbacks.
Go ecosystem (macOS): xorrior/goesf (CGO+ObjC shim); gatkinso/gomac/endpointsecurity (CGO wrapper); 0x4D31/santamon (sidesteps ES via Santa telemetry); ebitengine/purego (dlopen without CGO).
Go ecosystem (general): cilium/ebpf-go (foundation of Tetragon/Falco/Cilium); cilium/tetragon (CNCF eBPF agent, <1% CPU); awnumar/memguard (off-heap mlock'd secrets).
Compliance: CISA/NSA Memory-Safe Languages CSI (June 2025).

Alternatives Considered#

Go — purpose-driven analysis (2026-04-16)#

A thorough feasibility review evaluated Go against each functional purpose rather than comparing libraries.
Purposes Go fulfills well (11 of 13): The majority of DaemonEye's functional purposes are language-agnostic and Go can fulfill them — memory safety (via GC), privilege separation, audit trail with hash chains and Merkle proofs, custom detection DSL (participle/ANTLR4-Go/pigeon), alert delivery, offline-first operation, single-binary cross-compile for pure Go, CISA/NSA compliance (Go and Rust listed equivalently), extensible collector framework (interfaces), event bus (channels), and Linux eBPF (where Go is actually stronger via cilium/ebpf-go and Tetragon precedent).
Purposes with moderate gaps (2 of 13): Embedded database under 100 MB RSS (bbolt ACID-but-single-writer, Badger has documented RSS blowups — achievable with care but less margin than redb); process enumeration at scale (stock gopsutil has documented performance issues; Datadog maintains a custom fork — achievable but requires custom work).
Platform kernel collection deep-dive:

  • Linux eBPF — Go is stronger. cilium/ebpf-go is the foundation of Tetragon, Falco, Pixie, and Cilium.
  • Windows ETWGap closed. Pure-Go ETW consuming is proven via mkwinsyscall + NewCallbackCDecl; ProcessTrace invokes callbacks on the caller's thread, sidestepping Go's foreign-thread callback limitation. go-winio (MIT) provides named pipes and ETW producing without CGO. A clean-room implementation of ETW consuming is feasible (the technique is unencumbered — Microsoft public docs).
  • macOS EndpointSecurityNarrow gap. es_new_client() takes an Objective-C block, not a C function pointer. Mitigations: 30-line CGO shim; separate native process with protobuf IPC; the santamon pattern (read Santa telemetry from a spool directory).
  • Windows IPC / ETW producing — viable without CGO via go-winio.
    Genuine remaining gaps: Windows ETW consuming (closed — proven pure-Go technique, clean-room required); macOS EndpointSecurity (narrow — one Obj-C block, solvable with a 30-line CGO shim or IPC pattern); secret zeroization (open — Go's runtime/secret is experimental and Linux-only; memguard exists but adds complexity; Rust's zeroize/secrecy remain superior for FIPS-adjacent key handling).
    Verdict: Go can fulfill the vast majority of DaemonEye's functional purposes. The only genuinely open gap is cross-platform deterministic secret zeroization. Rejected as the primary language — but the gap is narrower than initially assessed, which enables the hybrid option below.

Hybrid Go + Rust architecture (evaluated 2026-04-16)#

Go handles the bulk of DaemonEye; Rust handles only the components where Go has genuine limitations. DaemonEye's existing protobuf IPC between separate processes means the language boundary and the process boundary are the same line — the cleanest possible hybrid, no FFI or CGO in the hot path.
Proposed component split:

  • Go: daemoneye-agent (detection engine, alert delivery, event bus, storage); daemoneye-cli (operator interface, queries, rule management); Linux eBPF (cilium/ebpf-go); Windows ETW (clean-room mkwinsyscall consumer).
  • Rust: procmond (elevated process collector, binary hashing, audit ledger, privilege drop); macOS ESF (EndpointSecurity client with Obj-C block); crypto-core (secret handling, key derivation, signing via zeroize/secrecy).
  • Communication: protobuf IPC over Unix sockets / named pipes, unchanged.
    Gains: better Linux eBPF ecosystem; faster development for agent/CLI/detection engine; simpler concurrency via Go channels; broader contributor pool for SIEM integrations. Costs: two toolchains (cargo + go), two CI matrices, two dependency audit pipelines; two test ecosystems; integration testing complexity; open-source crate story changes (connectors as Go libs or Rust-only for procmond); cognitive overhead for contributors needing both languages.
    Verdict: Technically viable and architecturally clean. For a single-maintainer project, the operational cost of two full toolchains outweighs the development velocity gains. Documented as a viable future option if team size grows or if a Linux-first product variant is pursued. See ADR-0003 (Polyglot Collector SDK Strategy) for the collector-specific approach that does adopt a hybrid model at the collector layer.

C++#

Maximum performance, mature ecosystem, no runtime overhead. Manual memory management increases development time and security risks; does not satisfy CISA memory-safe language guidance. Rejected — development velocity and safety requirements favor Rust.

Keeping Python#

Existing codebase, familiar technology, rapid development. Performance limitations (GIL, object overhead), deployment complexity (runtime + venvs + dependency management), security concerns (dynamic typing expands attack surface). Rejected — performance requirements (Purpose 1) cannot be met.

ADR-0001 - Adopt Rust for DaemonEye Engine | Dosu