Real-time security event correlation, in a single Rust process.
Write Sigma rules, feed it OCSF events, get alerts — no cluster, no JVM, no brokers.
Secrust ingests OCSF-compliant security events, evaluates them against detection rules, and emits alerts — inside one process, across all your cores. It borrows Apache Flink's ideas about stream processing (event time, keyed state, windows) without asking you to run Flink.
git clone https://github.com/BlueSquadron/Secrust.git
cd Secrust
./scripts/demo.shThat replays a seeded attack — a brute force that succeeds, beaconing, tampered audit logs, a password spray — through a five-file rule pack covering all four detection strategies. Real output, no mock-ups:
Seven alerts from 27 events. The interesting one is lateral_movement: no single
event is suspicious, but many failures followed by a success for the same user
is an account takeover, and only a correlation rule can say so.
Before the replay, the demo shows the rules it compiled and the worker layout the engine derived from them — you never configure threads or partitions yourself:
Everything it just ran lives in demo/: commented rules, the
seed events, and a walk-through of the scenario minute by minute. Change a
threshold, add an event, run it again.
| One binary, all cores | The engine reads your rules, then builds its own worker pools: stateless rules run inline on the submitting thread, keyed counters get hash-partitioned pools, stateful state that must not be split gets a single worker. |
| Sigma in, alerts out | Detection rules are Sigma YAML — including multi-document correlation rules — so they are reviewable, diffable and portable. Native JSON rules for machines. |
| OCSF-native | Events follow the Open Cybersecurity Schema Framework, so anything that speaks OCSF speaks Secrust. |
| Event time, not arrival time | Replaying yesterday's logs produces exactly the alerts that would have fired yesterday. |
| Embeddable | Rust API, C FFI (Python, Go, Java, Node.js) or gRPC — pick one. Alerts go wherever you send them via the AlertSink trait. |
| Fast enough to stop thinking about it | ~700K events/sec on a laptop for a mixed rule set, 1.1M+/sec for stateless matching — on one machine, no tuning. |
| Strategy | Sigma shape | Fires when |
|---|---|---|
| Value match | no timeframe |
an event matches a field pattern (wildcards, regex, contains/startswith/endswith) |
| Counter | timeframe + count(...) |
N matching events within a duration, optionally per key |
| Tumbling window | timeframe |
a fixed window collects N events, or N distinct values of a field |
| Correlation | correlation: document |
several rules fire for the same entity within a timespan — in any order (temporal) or in sequence (temporal_ordered) |
The rule that catches the takeover in the demo:
title: Brute Force Followed by Successful Logon
id: lateral_movement
correlation:
type: temporal_ordered
rules:
- logon_failures # counter: >= 5 failures in 10m
- logon_success # counter: >= 1 success in 10m
group-by:
- user.name
timespan: 10m
generate: false # only the correlation alert is emittedFull syntax: docs/detection-rules.md. Sharp edges worth five minutes: docs/gotchas.md.
Rust
use secrust::engine::EngineBuilder;
use secrust::models::Alert;
use secrust::traits::{AlertSink, AlertSinkError};
use std::path::Path;
struct Stdout;
impl AlertSink for Stdout {
fn receive_alert(&self, alert: &Alert) -> Result<(), AlertSinkError> {
println!("[{}] {} — {} event(s)", alert.timestamp, alert.rule_name, alert.matched_events.len());
Ok(())
}
}
let engine = EngineBuilder::new()
.parallelism(4)
.load_sigma_directory(Path::new("demo/rules"))?
.add_alert_sink(Box::new(Stdout))
.build()?;
engine.submit_event(r#"{
"metadata": {"version": "1.1.0"},
"severity_id": 5, "class_uid": 3001, "category_uid": 3,
"activity_id": 2, "type_uid": 300102,
"time": 1773478980000,
"user": {"name": "mallory"},
"src_endpoint": {"ip": "203.0.113.66"}
}"#)?;
engine.shutdown()?; // drains in-flight events, then joins every threadAdd the dependency with secrust = { git = "https://github.com/BlueSquadron/Secrust" }
(the crate is not on crates.io yet).
Python, Go, Java, Node.js — via the C FFI
cargo build -p secrust-ffi --release # → target/release/libsecrust_ffi.{dylib,so,dll}import ctypes, json
lib = ctypes.CDLL("target/release/libsecrust_ffi.dylib")
ALERT_CB = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_void_p)
def on_alert(alert_json, _user_data):
print("ALERT", json.loads(alert_json.decode())["rule_id"])
config = json.dumps({"parallelism": 4, "rules": {"rules": [
{"type": "value_match", "id": "critical", "name": "Critical",
"field": "severity_id", "pattern": "5"}]}})
engine, err = ctypes.c_void_p(), ctypes.c_char_p()
lib.sec_engine_new(config.encode(), ALERT_CB(on_alert), None,
ctypes.byref(engine), ctypes.byref(err))Full C API in docs/developer-guide.md.
Any language — via gRPC
brew install protobuf # or: apt install protobuf-compiler
make server # listens on [::1]:50051SubmitEvent, SubmitEventStream, SubscribeAlerts, GetCounterStates,
GetWindowStates, Shutdown — schema in
secrust-server/proto/secrust.proto.
Single run on an Apple Silicon laptop, release build, 100K OCSF authentication
events per scenario. The quick one, ./scripts/demo.sh --bench:
The full matrix, from cargo run -p secrust --release --example parallelism_research
(p = worker threads per keyed pool):
| Workload | p=1 | p=2 |
|---|---|---|
| Value match only (stateless) | 1,154K/sec | 1,580K/sec |
| Counter, ungrouped (singleton pool) | 1,739K/sec | 1,728K/sec |
| Counter, 256 distinct keys | 802K/sec | 1,508K/sec |
| Mixed (value match + counter + window) | 701K/sec | 1,038K/sec |
| Mixed + temporal correlation | 702K/sec | 1,074K/sec |
Past p=2 the numbers flatten or dip: fan-out cost grows with pool count while the submitting thread stays the bottleneck. docs/parallelism-research.md has the analysis, the optimization history (+301% over eight stages) and the next bottlenecks — a good map if you want to make it faster.
submit_event(json)
├── single-pass serde parse (no flatten buffering)
├── value match rules evaluated inline, zero allocation
└── Arc<Event> fan-out to the pools the rules asked for:
├── singleton pool (1 worker) — ungrouped counters, windows, correlations
└── keyed pool(s) (N workers) — counters grouped by hash(field)
↓
alerts → dispatcher thread → your AlertSink(s)
secrust/ engine, evaluators, Sigma translation, worker pools
examples/demo.rs the guided demo you ran above
secrust-ffi/ C ABI shared library
secrust-server/ gRPC server (tonic)
demo/ seed events + commented rule pack
rules/ a handful of standalone example rules
docs/ guides, internals, research
Deeper: docs/internals.md — pipeline, thread model, channels, state expiry.
cargo test -p secrust # 163 tests: 141 unit, 8 integration, 14 property (~15s)
make build # debug build, whole workspace
make bench-throughput # quick events/sec report
make server # gRPC server on [::1]:50051
make doc # rustdocRust 1.70+ (rustup update stable) is all you need for the library, the tests
and the demo. protoc is required only to build secrust-server
(brew install protobuf / apt install protobuf-compiler) — the workspace-wide
targets (make build, make test) include it, so install it or scope your
commands with -p secrust.
Issues and pull requests are welcome — detection rules, docs and benchmarks just as much as engine code. CONTRIBUTING.md explains the layout, how to run the checks, and what a good rule contribution looks like; docs/gotchas.md doubles as a list of known limitations if you are looking for something to fix.
Everyone taking part is expected to follow the Code of Conduct. Security issues: please read SECURITY.md first.
| Guide | What is in it |
|---|---|
| demo/README.md | The demo pack, the seeded scenario, how to modify it |
| Detection rules | Sigma and JSON rule syntax, every rule type |
| Gotchas | Verified sharp edges and current limitations |
| Developer guide | Event format, Rust API, C FFI, gRPC |
| Internals | Architecture, pipeline, thread and channel design |
| Parallelism research | Benchmarks and optimization history |
| ATT&CK coverage | Detection coverage vs. MITRE ATT&CK |
| Flink comparison | What Secrust does and does not take from Flink |
MIT — see LICENSE.