Engineering Rust Architecture

Why We Wrote an EDR in Rust

March 2025 · 8 min read

We get this question a lot. Why Rust? Why not Go, which most security tooling uses? Why not C++, which has decades of systems programming history in this space?

The answer isn't ideology. It's arithmetic. Specifically, the arithmetic of running a security agent on the kind of hardware that K-12 school districts and community hospitals actually own.

The hardware problem

When we started talking to school district IT directors, we kept hearing the same thing: "Our endpoints are 6-8 years old. We can't afford to refresh them. Any new software has to be invisible."

A CrowdStrike Falcon sensor on a 2017 Dell Optiplex with an i5-7400 and 8 GB of RAM is not invisible. It uses 2-5% CPU at baseline. On a machine that students are already pushing hard running a browser with 20 tabs, that overhead is felt. Deployment complaints pile up. IT support tickets spike. Principals start calling the IT director.

We needed sub-0.5% CPU at idle. That requirement alone narrowed the language choice to basically two options.

Go was close, but not close enough

Go is excellent. Most of the security tooling we admire — Wazuh's agent components, Teleport's node agents, much of Datadog's infrastructure — is written in Go. But Go has a garbage collector, and garbage collectors have a cost.

The Go GC is fast and well-tuned. For a web service or a CLI tool, you'll never notice it. But for a background process on a constrained endpoint that needs to process 500,000 telemetry events per minute (file opens, process starts, network connections, registry reads) GC pauses matter. Not because they're long, but because they're non-deterministic. A 1ms GC pause 100 times per second is 10% of your CPU budget gone.

We built a prototype in Go. It worked. But we couldn't get sustained idle CPU below 1.2% on constrained hardware without sacrificing detection coverage.

Rust gives you memory safety without a garbage collector. That has a bunch of practical consequences. Event processing is deterministic — no GC pauses. We process telemetry at 500K events/minute with microsecond latency variance, not millisecond. On the endpoint, the agent is genuinely invisible. We measure 0.3% CPU at steady state on i5-6th gen hardware.

Memory safety matters more for security software than almost anything else. Our agent processes attacker-controlled input: file contents, process command lines, network packet payloads. A memory-unsafe EDR agent is a foothold for the attackers you're supposed to be catching. We literally can't ship a buffer overflow or a use-after-free. The compiler catches it before we can commit it.

The async story is solid. We use Tokio for all I/O — gRPC telemetry streaming, file system event processing, network connection monitoring. We run 11 concurrent gRPC streams per agent with no thread-per-connection overhead.

Cross-platform works without compromise. The same Rust code compiles to native binaries for Windows (x64), macOS (x64 and Apple Silicon), and Linux. No JVM to install. No interpreter to ship. The MSI is 12 MB. The DEB is 9 MB. They install in seconds.

What it cost us

Rust has a reputation for a steep learning curve. That reputation is accurate. The borrow checker is genuinely different from how most programmers think about memory. It took us a few months to stop fighting it and start working with it.

The payoff is that bugs you'd spend days debugging in C++ or Go simply don't compile. We've had zero memory safety incidents in production. Not because we're especially careful, but because the language won't let us be careless.

The async ecosystem isn't as mature as Go's. Some libraries that exist for Go don't exist for Rust, and we had to build more primitives ourselves. We wrote our own YARA rule engine integration, our own Windows network monitoring layer, our own procfs-based Linux network monitor. In Go, we might have reached for a library. In Rust, we wrote lean, zero-allocation code that does exactly what we need. Whether that's a net win is honestly hard to say. We think so, but we're biased.

The agent is built on Tokio, Tonic (gRPC), Prost (protobuf), Notify (file system events), Sysinfo (process metrics), and aes-gcm (AES-256-GCM quarantine encryption). The backend runs on Axum 0.8 with SurrealDB 2.x. The console is Flutter — the only realistic choice for true cross-platform native desktop without shipping an Electron app.

0.3% CPU idle on a 2017 i5. 12 MB installer. Zero memory safety CVEs. 500K events/minute throughput. Windows, macOS, and Linux from a single codebase.

If you're building infrastructure software that needs to be invisible, safe, and fast, Rust is a very good answer. We made that bet in 2024 and haven't regretted it.

You can try Bastion on 25 endpoints for 14 days, free — start here.

More from the blog