High-performance, user-mode Windows EDR in Rust
Rustinel is a high-throughput Windows EDR agent written in Rust. It collects kernel telemetry via ETW, normalizes events into a Sysmon-compatible schema, detects threats using Sigma + YARA, and outputs alerts as ECS NDJSON for straightforward SIEM ingestion.
✅ No kernel driver
✅ User-mode ETW pipeline
✅ Sigma behavioral detection + YARA scanning
✅ ECS NDJSON alerts + operational logs
Rustinel is built for defenders who want:
- Kernel-grade telemetry without kernel risk (ETW, user-mode)
- Performance under volume (async pipeline + caching + noise reduction)
- Detection compatibility (Sysmon-style normalization for Sigma)
- Operational simplicity (NDJSON alerts on disk, easy to ship to a SIEM)
Rustinel monitors Windows endpoints by:
- Collecting kernel events via ETW (process, network, file, registry, DNS, PowerShell, WMI, services, tasks)
- Normalizing ETW events into Sysmon-compatible fields
- Detecting threats using Sigma rules and YARA scanning
- Detecting atomic IOCs (hashes, IP/CIDR, domains, path regex)
- Writing alerts in ECS NDJSON format
- User-mode only: no kernel driver required
- Dual detection engines:
- Sigma for behavioral detection
- YARA for file scanning on process start
- Atomic IOC detection: hashes, IP/CIDR, domains, path regex
- Noise reduction:
- keyword filtering at the ETW session
- router-level filtering for high-volume network events
- optional network connection aggregation
- Hot-path optimizations:
- Sigma rules are filtered at load time (
category/product/service) - Sigma conditions are transpiled + precompiled at startup
- process-context enrichment is attached on alerts, not every event
- Sigma rules are filtered at load time (
- Enrichment:
- NT → DOS path normalization
- PE metadata extraction (OriginalFileName/Product/Description)
- parent process correlation
- SID →
DOMAIN\Userresolution - DNS caching and reverse mapping
- Windows service support (install/start/stop/uninstall)
- ECS NDJSON alerts for SIEM ingestion
- Optional active response (dry-run or terminate on critical alerts)
- Windows 10/11 or Server 2016+
- Administrator privileges (ETW + service management)
- Rust 1.92+ (build from source)
Run from an elevated PowerShell.
Option 1: Download Release (Recommended)
- Download the latest release from GitHub Releases.
- Extract the archive.
- Run from an elevated PowerShell:
.\rustinel.exe run --console
Option 2: Build from Source
# Build
cargo build --release
# Run (console output)
.\target\release\rustinel.exe run --consoleRunning without arguments is equivalent to rustinel run.
This repo ships with an example rule: rules/sigma/example_whoami.yml
- Start Rustinel (admin shell):
cargo run -- run --console- Trigger the rule:
whoami /all- Verify an alert was written:
logs/alerts.json.YYYY-MM-DD
This repo ships with an example rule: rules/yara/example_test_string.yar
- Build the demo binary:
rustc .\examples\yara_demo.rs -o .\examples\yara_demo.exe- Run it:
.\examples\yara_demo.exe- Verify an alert includes the rule name:
ExampleMarkerString
Note: The demo binary runs in a loop to demonstrate active response. With response enabled and prevention_enabled = true, the process will be automatically terminated when the YARA rule triggers (YARA matches are treated as critical severity).
.\target\release\rustinel.exe service install
.\target\release\rustinel.exe service start
.\target\release\rustinel.exe service stop
.\target\release\rustinel.exe service uninstallNotes
service installregisters the current executable path — run it from the final location.- Config and rules paths resolve from the working directory; for services, prefer absolute paths or env overrides.
- Service runtime does not receive CLI flags; set log level via
config.tomlorEDR__LOGGING__LEVEL.
Configuration precedence:
- CLI flags (highest, run mode only)
- Environment variables
config.toml- Built-in defaults
Example config.toml:
[scanner]
sigma_enabled = true
sigma_rules_path = "rules/sigma"
yara_enabled = true
yara_rules_path = "rules/yara"
[allowlist]
paths = [
"C:\\Windows\\",
"C:\\Program Files\\",
"C:\\Program Files (x86)\\",
]
[logging]
level = "info"
directory = "logs"
filename = "rustinel.log"
console_output = true
[alerts]
directory = "logs"
filename = "alerts.json"
match_debug = "off" # off | summary | full
[response]
enabled = false
prevention_enabled = false
min_severity = "critical"
channel_capacity = 128
allowlist_images = []
[network]
aggregation_enabled = true
aggregation_max_entries = 20000
aggregation_interval_buffer_size = 50
[ioc]
enabled = true
hashes_path = "rules/ioc/hashes.txt"
ips_path = "rules/ioc/ips.txt"
domains_path = "rules/ioc/domains.txt"
paths_regex_path = "rules/ioc/paths_regex.txt"
default_severity = "high"
max_file_size_mb = 50allowlist.paths is shared by default across:
response.allowlist_pathsioc.hash_allowlist_pathsscanner.yara_allowlist_paths
If a module-specific list is explicitly set, it overrides the shared list for that module only.
Match debug output:
alerts.match_debug = "off"disables match details in alerts (default).alerts.match_debug = "summary"adds rule condition + matched fields/patterns.alerts.match_debug = "full"adds matched values and YARA string snippets.
Environment overrides:
set EDR__LOGGING__LEVEL=debug
set EDR__SCANNER__SIGMA_RULES_PATH=C:\rules\sigma
set EDR__ALLOWLIST__PATHS=["C:\\Windows\\","C:\\Program Files\\"]
# optional module-specific override:
set EDR__SCANNER__YARA_ALLOWLIST_PATHS=["C:\\Windows\\","D:\\Trusted\\"]CLI override (highest precedence, run mode only):
rustinel run --log-level debugNote: rule logic evaluation errors are only logged at warn, debug, or trace levels (suppressed at info).
| Option | Default | Description |
|---|---|---|
enabled |
false |
Enable active response engine |
prevention_enabled |
false |
If false, logs dry-run actions only |
min_severity |
critical |
Minimum severity to respond to (Sigma uses rule level, YARA is always treated as critical) |
channel_capacity |
128 |
Queue size for response tasks (drops on overflow) |
allowlist_images |
[] |
Image basenames or full paths to skip |
allowlist_paths |
inherits allowlist.paths |
Prefix paths to skip (case-insensitive). Optional module-specific override |
More details: docs/active-response.md.
| Option | Default | Description |
|---|---|---|
enabled |
true |
Enable atomic IOC detection |
hashes_path |
rules/ioc/hashes.txt |
Hash IOC file (MD5/SHA1/SHA256) |
ips_path |
rules/ioc/ips.txt |
IP and CIDR IOC file |
domains_path |
rules/ioc/domains.txt |
Domain IOC file |
paths_regex_path |
rules/ioc/paths_regex.txt |
Path/filename regex IOC file |
default_severity |
high |
Severity assigned to IOC alerts |
max_file_size_mb |
50 |
Skip hashing files larger than this (MB) |
hash_allowlist_paths |
inherits allowlist.paths |
Prefix paths to skip hashing (case-insensitive). Optional module-specific override |
- Place
.yml/.yamlfiles underrules/sigma/ - Supported categories include:
process_creation,network_connection,file_event,registry_event,dns_query,image_load,ps_script,wmi_event,service_creation,task_creation
- Place
.yar/.yarafiles underrules/yara/ - Rules compile at startup
- Scans trigger on process creation (runs in a background worker)
- Files under allowlisted path prefixes are skipped (
allowlist.pathsby default orscanner.yara_allowlist_pathsoverride)
Place indicator files under rules/ioc/:
hashes.txt— MD5, SHA1, SHA256 hashes (auto-detected by length)ips.txt— IP addresses and CIDR rangesdomains.txt— exact domains or*./.prefix for suffix matchingpaths_regex.txt— case-insensitive regexes matched against file paths
Hash checking runs in a dedicated background worker on process creation. Files under allowlisted paths (shared allowlist.paths by default, or ioc.hash_allowlist_paths override) and files exceeding max_file_size_mb are skipped automatically. Domain, IP, and path checks run inline with negligible overhead.
Rustinel produces:
- Operational logs:
logs/rustinel.log.YYYY-MM-DD - Security alerts (ECS NDJSON):
logs/alerts.json.YYYY-MM-DD
Example alert (one JSON object per line):
{
"@timestamp": "2025-01-15T14:32:10Z",
"event.kind": "alert",
"event.category": "process",
"event.action": "process_creation",
"rule.name": "Whoami Execution",
"rule.severity": "low",
"rule.engine": "Sigma",
"process.executable": "C:\\Windows\\System32\\whoami.exe",
"process.command_line": "whoami /all",
"user.name": "DOMAIN\\username"
}# Unit tests
cargo test
# Format + lint
cargo fmt
cargo clippy
# Validate Sigma + YARA rules
cargo run --bin validate_rulesProject layout (high level):
src/
├── collector/ # ETW collection + routing
├── normalizer/ # Sysmon-style normalization + enrichment
├── engine/ # Sigma engine
├── scanner/ # YARA scanning worker
├── ioc/ # Atomic IOC detection (hashes, IPs, domains, paths)
├── state/ # caches (process/sid/dns/aggregation)
└── bin/validate_rules.rs
Short roadmap:
- YARA expansion (memory scanning + periodic scans).
- Resource governor (Windows Job Objects CPU limits).
- Self-defense hardening (DACL/ACL restrictions + anti-injection).
- Watchdog sidecar to restart the service if the main process dies.
- ETW integrity checks to detect blinding/tampering.
- Deep inspection via stack tracing for "floating code".
Rustinel is Alpha. It’s usable for experimentation, lab deployments, and iterative hardening. Expect breaking changes while the schema + engines mature.
Apache 2.0 — see LICENSE.
