Why we built BrokenApp in Rust
When we started building BrokenApp, we had a list of non-negotiables: sub-second startup, concurrent browser sessions without memory leaks, a single binary with zero runtime dependencies, and structured JSON output that's machine-parseable. We evaluated Go, TypeScript, and Python before landing on Rust. Here's why.
The constraints
A web application scanner does a lot of things concurrently. It launches headless browsers, makes HTTP requests, parses HTML, evaluates JavaScript, matches regex patterns against response bodies, and writes structured output — all at the same time, for dozens or hundreds of endpoints.
In a garbage-collected language, this kind of workload leads to unpredictable memory spikes, GC pauses during time-sensitive operations, and slowly growing RSS that forces you to restart long-running scans. We needed deterministic memory behavior.
Why not Go?
Go was our closest runner-up. Fast compilation, great concurrency primitives, single binary output. But Go's garbage collector, while excellent for servers, introduces latency variance that matters when you're coordinating headless browser sessions with tight timeouts.
More importantly, Rust's type system catches entire categories of bugs at compile time that Go catches at runtime (or doesn't catch at all). For a security tool, correctness isn't optional. A false positive in a security scanner erodes trust immediately.
What Rust gives us
Zero-cost abstractions
Our regex engine compiles 18 secret-detection patterns at startup and matches them against every response body with zero allocation per match. In a GC language, this would require careful pooling to avoid allocation pressure.
Fearless concurrency
Tokio lets us run dozens of concurrent browser sessions, HTTP requests, and file I/O operations. Rust's ownership model guarantees no data races at compile time. We've never had a concurrency bug in production.
Single binary distribution
cargo build --release produces a single binary. No runtime, no dependencies, no Docker. Users install it with one command and it works. This is the #1 reason developers actually adopt CLI tools.
Predictable performance
No GC pauses. No JIT warmup. No startup overhead. BrokenApp launches in ~50ms and maintains consistent throughput throughout the scan. Memory usage is proportional to the number of concurrent sessions, not accumulated garbage.
The tradeoffs
Rust isn't free. Compile times are slower than Go. Onboarding new engineers takes longer. Some things that are trivial in TypeScript (parsing arbitrary JSON, quick prototyping) require more upfront design in Rust.
But for a security tool that runs on other people's machines, handles sensitive data, and needs to be correct every time — the tradeoff is worth it. We'd rather spend an extra hour fighting the borrow checker than ship a tool that leaks memory on a 30-minute scan.
The ecosystem
The Rust ecosystem has matured dramatically. The crates we depend on are battle-tested:
tokio— async runtime for concurrent I/Ochromiumoxide— Chromium DevTools Protocol for headless browser automationreqwest— HTTP client with connection poolingregex— compiled regex with guaranteed linear-time matchingserde— zero-copy JSON serialization/deserializationblake3— cryptographic hashing for finding fingerprintsclap— CLI argument parsing with shell completions
Would we choose Rust again?
Absolutely. The CLI starts in 50ms. Memory usage stays flat across scans of any size. We've had zero memory-related bugs in production. The type system catches misconfigurations before they ship. And users install it with one command because it's a single binary.
For a different product — a web dashboard, a data pipeline, a quick prototype — we'd choose differently. But for a security-critical CLI tool that runs on other people's machines and handles sensitive data? Rust is the right call.