GarudaEye: Building a Cloud Asset Discovery Tool in Rust
There's a loop I kept running every time I wanted a quick security picture of an AWS environment.
Install the scanner. Grab the Shodan API key. Set up the Censys token. Figure out why the dashboard isn't connecting to the backend. Stitch together output from three different JSON dumps. An hour later, still not actually sure what's exposed.
I got tired of it. So I built GarudaEye.
The Problem With Existing Tooling
Most cloud security tools sit at one of two extremes. On one end you have heavyweight SaaS platforms — powerful, expensive, and your data lives somewhere else. On the other end you have CLI scripts that output wall-of-JSON and leave the interpretation entirely to you.
What I wanted was simpler: drop a binary on a machine, point it at my AWS account, and get a clear picture of what's out there. No tokens to manage beyond my AWS credentials. No separate web server. No Docker Compose file with six services just to run a scan.
That constraint — one binary, no external dependencies — shaped almost every architecture decision that followed.
Why Rust
Rust was an obvious choice once I committed to the single-binary goal. The ecosystem made it straightforward to compile a statically linked MUSL binary that runs on any x86_64 Linux machine with nothing installed. The release build comes out at roughly 20 MB and includes the entire Vue.js dashboard compiled in via build.rs.
Beyond the binary story, Rust's async runtime (Tokio) handles the concurrent scanning naturally. Collecting 17 different AWS resource types across multiple regions simultaneously, while a worker pool runs fingerprinting analysis on public assets in parallel — that's exactly the kind of workload Tokio is designed for.
// Simplified orchestrator — collectors run concurrently per region
let handles: Vec<_> = regions
.iter()
.flat_map(|region| collectors.iter().map(|c| c.collect(region)))
.map(tokio::spawn)
.collect();
for handle in handles {
handle.await??;
}
The compile times are painful. The borrow checker arguments are occasionally exhausting. I'd do it again.
What GarudaEye Actually Does
At its core, GarudaEye does three things:
- Discovers your AWS assets across every region — EC2, S3, RDS, Lambda, EKS, ECS, CloudFront, Route53, and 9 more service types.
- Fingerprints every public-facing resource passively — DNS resolution, TLS certificate inspection, HTTP/HTTPS header analysis, TCP banner grabbing, ASN/geo lookup.
- Reasons about risk — a risk score from 0 to 100 per asset, plus attack path analysis that identifies chains of exposure rather than isolated findings.
The dashboard lives at http://127.0.0.1:8080 and opens automatically. There's an asset inventory, a force-directed relationship graph, an attack surface view, and per-asset detail panels with everything the fingerprinting engine found.
The Fingerprinting Engine
This is the part I'm most proud of.
Security tools that do passive fingerprinting almost always punt on the "no API key required" promise. You get TLS inspection for free, but anything involving IP reputation or OS detection routes through Shodan or Censys. Fair enough — those are genuinely hard problems.
I wanted to see how far I could get with nothing but a TCP connection and some patience.
The answer: surprisingly far. The fingerprinting engine runs eight sub-modules per asset:
- DNS — A/AAAA/MX/TXT/SOA records, SPF/DMARC/DNSSEC detection, DNS provider identification
- TLS — Certificate chain, subject/SANs, expiry, self-signed detection
- HTTP/HTTPS — Status codes, server headers, page title, security header presence (HSTS, CSP)
- Banner grabbing — TCP banner on common ports mapped to product/version/CVE hints
- Cloud detection — AWS/Azure/GCP/Cloudflare identification from rDNS patterns, headers, and ASN
- ASN — Autonomous system and geolocation from IP
- Technology signatures — HTTP response pattern matching for frameworks and server software
- OS guessing — TTL-based and banner-based inference
All of this runs without a Shodan key. If you have a Shodan key you can plug it in and get richer IP data, but the engine is fully useful without one.
The risk score is a weighted composite — a public S3 bucket with public access enabled and no encryption hits differently than an EC2 instance behind a load balancer with valid TLS and all security headers present.
Attack Path Analysis
Individual findings are useful. Attack paths are actionable.
The attack path engine runs after fingerprinting completes and looks at relationships between assets, not just the assets themselves. A public EC2 instance is a finding. That same EC2 instance having a mapped relationship to an RDS cluster on port 3306 with public access enabled is a LateralMovement + ExposedDatabase attack path — and those are two different severities with two different remediation paths.
Right now GarudaEye detects seven path types:
| Path Type | Severity | What it catches |
|---|---|---|
ExposedDatabase | Critical | DB ports open to internet |
InsecureProtocol | Critical | FTP, Telnet, TFTP, SNMP |
WeakTLS | High | Self-signed, expired, or expiring certs |
ExposedAdminInterface | High | SSH, RDP, admin ports publicly exposed |
CVEVector | Critical | CVE hints on a public asset |
LateralMovement | High | Public asset with relationships to internal DBs/caches |
MissingSecurityHeaders | Medium | No HSTS or CSP on a public HTTPS endpoint |
More are coming. The relationship graph makes it possible to reason about blast radius in ways a flat asset list can't.
Two Runtime Modes
For local use, GarudaEye runs entirely in-memory with SQLite. Zero external dependencies. Ideal for running a scan, reviewing the results, and shutting it down.
For persistent deployments — say, running it as a scheduled job in your cloud environment — there's a cloud mode that swaps in Postgres and Redis:
export MODE=cloud
export DATABASE_URL=postgres://user:password@localhost:5432/garudaeye
export REDIS_URL=redis://localhost:6379
./garudaeye --mode cloud
The same binary, different backing services. The interface is identical.
What I'd Do Differently
The workspace structure in Cargo took a couple of iterations. I split things into crates early — garudaeye_core, infra, collectors, analyzers, api — which was the right call, but the boundaries shifted as the project matured. Getting the dependency graph right before you have the full picture of what each crate needs is genuinely hard.
DynamoDB to SQLite was another pivot. I originally reached for DynamoDB (habits from RiskProfiler), but for a tool that needs to run locally with zero infrastructure, embedding SQLite with sqlx was a much better fit. The migration was worth it.
Launching on Product Hunt
I shipped v0.1.0 and put it on Product Hunt mostly to see if the framing landed with people outside the security bubble. The response was better than I expected — the "no API keys" angle resonated, and the attack path engine got more attention than the fingerprinting engine, which surprised me.
The feedback that's shaping the roadmap: multi-cloud support. Azure came up more than I expected. GCP and DigitalOcean too. That's Phase 5.
Getting Started
If you want to try it:
git clone https://github.com/pranaypaine/GarudaEye
cd GarudaEye
cp .env.example .env
cd frontend && npm install && cd ..
cargo run -- --open
The dashboard opens at http://127.0.0.1:8080. You'll need AWS credentials in your environment — read-only IAM is enough, the required policy is in the README.
Or grab the pre-built binary from the Releases page. Static MUSL binary, just chmod +x and run.
Questions, feedback, or want to contribute? Reach out via email or LinkedIn. The repo is at github.com/pranaypaine/GarudaEye.