BrokenApp
Back to blog
EngineeringJan 22, 20265 min read

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:

  • tokioasync runtime for concurrent I/O
  • chromiumoxideChromium DevTools Protocol for headless browser automation
  • reqwestHTTP client with connection pooling
  • regexcompiled regex with guaranteed linear-time matching
  • serdezero-copy JSON serialization/deserialization
  • blake3cryptographic hashing for finding fingerprints
  • clapCLI 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.