Lavender is an EDR prototype built around a Rust eBPF endpoint agent, a NATS transport layer, and small Go backend services for ingest and telemetry handling.
The repository currently contains two active paths:
- a local detection path inside the Rust
agent - a backend transport path where the agent publishes raw telemetry to NATS and
services/ingestrepublishes canonical events
- eBPF tracepoints for
execve,sched_process_exit,openat, andconnect - local JSON stdout/stderr output for exec events, connection events, alerts, and response actions
- local detection and correlation for shell spawn, sensitive file read, suspicious port, reverse-shell-style chains, and related scoring/response
- outbound NATS publishing for raw
exectelemetry andheartbeatevents - Go ingest service that validates raw transport messages and republishes canonical telemetry
- Go telemetry-writer service that consumes canonical
execevents - runtime configuration via
lavender.toml
agent: Rust userspace agent that loads probes, consumes ring buffers, runs local detections, and publishes transport eventslavender-ebpf: Rust eBPF programs and ring buffer map definitionscommon: shared Rust event structs for the kernel/userspace boundary and transport schemaservices/ingest: Go service that validates raw transport events and republishes canonical telemetryservices/telemetry-writer: Go service that consumes canonical telemetry and shapes exec rowsdocs: project documentation and implementation notesdocker: local development container definitions
- docs/CONFIGURATION.md
- docs/EVENT_STREAMS_AND_INTERFACES.md
- docs/ROADMAP.md
- docs/ARCHITECTURE_MIGRATION_PLAN.md
- docker/compose/README.md
- docker/nats/README.md
flowchart LR
subgraph "Data Plane (Rust)"
E["Endpoint Agent"]
S["Minimal Local Spool / Buffer"]
E --> S
end
E <-->|"Telemetry, heartbeats, commands, policy"| Q["NATS / JetStream"]
I["Ingest Tier (Go)
validate, rate-limit, timestamp"]
Q --> I
I --> CE["Canonical Event Stream"]
subgraph DetectionPlane["Detection Plane (Go)"]
D["Detection Workers"]
R["Rule / Policy Config"]
CS["Correlation State (Redis/shared)"]
R --> D
CS --> D
end
CE --> D
CE --> L["Live Views / Telemetry Stream"]
D --> AL["Alerts Subject"]
CE --> P["PostgreSQL (TimescaleDB for telemetry)"]
Q --> H["Heartbeat Subjects"]
H --> C["Control Plane (Go)"]
C -- "Publish commands / policy" --> Q
C --> R
C --> CS
AL --> C
U["Dashboard / API (Go +TS UI)"] --> C
U --> P
C -- "WebSocket / SSE" --> U
L --> C
That diagram is the target direction. The current codebase only implements part of it: agent publishing, ingest canonicalization, and a telemetry-writer consumer. Detection workers, control-plane services, storage, and dashboard components are still planned work.
- Linux kernel with BTF enabled (
/sys/kernel/btf/vmlinux) - Rust toolchain and
cargo rustupnightly toolchain withrust-srcbpf-linkerinPATHsudoor root privileges to attach eBPF programs
Recommended setup:
rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
cargo install bpf-linkerBuild the Rust agent from the repository root:
cargo build --package agentagent/build.rs builds lavender-ebpf for the BPF target and embeds the artifact path into the userspace binary.
Build only the eBPF crate directly:
cd lavender-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --releaseBuild and run the full local stack from the repository root:
docker compose up --buildThis builds and starts:
natsingesttelemetry-writeragent
Run it in the background:
docker compose up --build -dWatch logs:
docker compose logs -f nats ingest telemetry-writer agentStop the stack:
docker compose downStart only the local NATS broker:
docker compose -f docker/nats/compose.yml up -dRun the containerized test workflow:
docker compose -f docker-compose.test.yaml up --build --abort-on-container-exitMore detail is in docker/compose/README.md and docker/nats/README.md.
Rust agent tests:
cargo test -p agent --testsWorkspace Rust tests:
cargo test --workspaceGo ingest tests:
cd services/ingest
go test ./...Go telemetry-writer tests:
cd services/telemetry-writer
go test ./...Containerized test workflow:
docker compose -f docker-compose.test.yaml up --build --abort-on-container-exitNote: docker-compose.test.yaml currently runs the Rust agent tests and ingest tests, but not the telemetry-writer tests.
Build as your normal user:
cargo build --package agentThen run the compiled binary with sudo:
sudo ./target/debug/agentAvoid running Cargo itself with sudo.
On success, you should see:
Lavender is watching. Ctrl+C to stop
- broker-only workflow: docker/nats/README.md
- full local stack: docker/compose/README.md
Configuration reference:
Current stream names, transport subjects, JSON payload shapes, and eBPF map names are documented in:
- Build the agent as a normal user and run only the compiled binary with
sudo. - The agent still performs local detection and active-response decisions itself.
- The backend transport path currently publishes only
execandheartbeatevents. open,connect, andexitevents are still handled locally and are not yet emitted on the NATS transport path.- For realistic host telemetry, run
natsandingestin Docker and run the Rust agent on the host againstnats://127.0.0.1:4222. - For quick end-to-end verification:
cargo test -p agent --tests
cargo build --package agent
docker compose up --build -d nats ingest telemetry-writer
sudo ./target/debug/agentIn another terminal, subscribe to the subject families you care about:
nats sub "telemetry.raw.>"
nats sub "telemetry.accepted.>"
nats sub "heartbeat.>"