Air-gapped by design: a vuln manager that doesn't phone home
Part of my series on RiskRancher, the open-source vulnerability manager I build. Source: github.com/Kuebiko-LLC/risk-rancher-core.
Intro
A vulnerability manager holds some of the most sensitive data an organization has: a map of exactly where it’s weak. So “where does this data go?” is a fair first question, and for a lot of teams, especially on isolated or classified networks, the only acceptable answer is “nowhere.” RiskRancher is built so that findings stay on the machine you run it on. In this post I want to be precise about what that means, including the one place it talks to the network, because I’d rather you trust a claim I can back up than a slogan.
I. What “air-gapped” means here
Two concrete promises:
- No telemetry. The app doesn’t report usage, crashes, or anything else to me or anyone. I don’t know who runs it or what’s in it, by design.
- No background network calls. The server doesn’t reach out on its own. It isn’t checking a license server, pulling config from a cloud, or syncing anything. Start it on a box with the network cable pulled and it runs exactly the same.
Your findings live in the local SQLite file and never leave it. The whole point of running a vuln manager on-prem is defeated if it quietly ships your weak spots somewhere, so it doesn’t.
II. The one exception, and why it’s fine
There’s exactly one spot in the app that can touch the internet, and I want to name it rather than hide it: the Check for updates button in the admin area. When you click it, and only when you click it, the server asks the releases API whether a newer version exists:
// HandleCheckUpdates pings the release API. If air-gapped, it returns manual instructions.
client := http.Client{Timeout: 3 * time.Second}
resp, err := client.Get(releasesURL)
if err != nil || resp.StatusCode != http.StatusOK {
respPayload.Status = "offline"
respPayload.Message = "No internet connection detected. To update an air-gapped server: " +
"download the latest binary on a connected machine, transfer it via rsync or scp, and restart."
// ...return gracefully
}
Two things make this acceptable. First, it’s opt-in: nothing happens until a human clicks the button. Second, it fails gracefully. On an air-gapped box the call times out in three seconds and the app tells you how to update by hand, with rsync or scp. It never blocks, never retries in the background, and never sends anything about your data. It only asks, “is there a newer tag?”
III. Why I’d rather tell you than claim zero
It would be cleaner marketing to say “zero outbound calls, full stop.” But that wouldn’t be true, and a security tool that overstates its own guarantees is exactly the kind of thing its users should distrust. The honest version is actually stronger: the app makes no calls on its own, and the single call it can make is one you trigger, that carries none of your data, and that degrades to manual instructions offline.
And it’s verifiable. That’s the real test for an air-gap claim. Run RiskRancher, watch the box’s outbound traffic, and use the whole app. You’ll see nothing leave until you deliberately click Check for updates. You don’t have to take my word for it, which is the point.
IV. How the rest of the design backs it up
The air-gap isn’t one feature, it’s a consequence of choices I’ve written about already. The single binary and SQLite mean there’s no external datastore to connect to. The pure-Go, no-CGO build means there are no surprise runtime dependencies that could reach out. When the whole app is one self-contained file carrying its own database, “it doesn’t phone home” stops being a promise you have to police and becomes something the architecture makes true by default.
Conclusion
Air-gapped should mean your data stays put, and it should be something you can check rather than something you’re told. RiskRancher sends no telemetry and makes no calls on its own; the only outbound traffic is an update check you click, that shares nothing and works offline. I’d rather hand you a claim with its one asterisk spelled out than a rounder number that doesn’t survive a packet capture. If you ship something that promises isolation, name your exceptions, because your users are the kind of people who will find them. Thanks for reading, and may your findings stay on your own machine.