1.28 Billion Fuzzer Runs Later: What We Still Don't Know About Our Vault Parser
Fuzzing proves the absence of crashes, not the absence of bugs. Inside OxidVault's cargo-fuzz setup for the vault file, SSH key, and audit log parsers — and what the numbers actually mean.
- security
- rust
- fuzzing
- cryptography
Parsers are where things go wrong
Most cryptographic vulnerabilities don’t live in the cipher. AES-256-GCM is well-studied; Argon2id is well-studied. The place where password managers actually break is much more mundane: the code that reads bytes off disk and decides what they mean.
A vault file, an SSH private key, an audit log line — all of them are untrusted input the moment they’re read back. A corrupted header, a truncated length field, a hand-crafted .oxid file dropped into the wrong folder — any of these can turn a parser into an attack surface, independent of whether the cryptography behind it is sound. Rust’s memory safety rules out entire classes of these bugs by construction, but it doesn’t rule out logic errors, panics, or integer overflows in length-prefixed parsing — and a panic in the wrong place is still a denial-of-service, even if it’s not a memory corruption.
That’s the part of the codebase we decided to fuzz.
What we’re actually testing
Fuzzing doesn’t run the whole app. It runs an isolated, byte-level entry point over and over with mutated input, looking for panics, crashes, or hangs. We defined three targets, each corresponding to a real parser boundary in vault-core:
| Target | Entry point | Scope |
|---|---|---|
vault_format |
format::parse_vault_file_bytes |
v4 magic bytes, header, users_json (incl. base64 field validation), payload nonce/ciphertext split — parsing only, no decryption |
audit_log |
audit::parse_audit_log_bytes, audit::verify_audit_chain_bytes |
Line format, hash-chain field extraction and verification |
ssh_key |
ssh_key_parse::parse_ssh_private_key_bytes |
PEM/PPK normalization, envelope checks, key-type classification |
Each of these takes attacker-controlled bytes before any cryptographic operation happens — exactly the layer where a malformed file should fail cleanly instead of taking the process down with it.
The numbers
Running under cargo-fuzz (libFuzzer + AddressSanitizer), the extended sessions so far report:
- ~1.28 billion runs on the v4 vault file parser
- ~164 million runs on the SSH key parser
- ~215 million runs on the audit log parser
Zero crashes, zero panics across all three. The fuzz targets and seed corpus live in fuzz/ in the repository — anyone can reproduce this with cargo +nightly fuzz run vault_format on Linux or WSL2.
What this does — and doesn’t — prove
Here’s the honest part. A clean fuzzing run is evidence of absence, not absence of evidence. It means: within the input space the fuzzer actually explored, in the time it ran, nothing crashed. It does not mean the parser is correct, that it rejects all malformed input the way it should, or that there’s no logic bug lurking in a code path the mutator never found its way into. Fuzzing is good at finding memory-safety issues, panics, and infinite loops. It is not a substitute for a specification review, and it says nothing about whether the cryptographic logic downstream of a successful parse is correct — that’s a different kind of testing entirely, covered separately by our cargo test suite for encryption, vault format, and auth flows.
We also excluded the fuzz/ workspace member from the root Cargo workspace, so it doesn’t affect regular builds, and we’ve kept the CI fuzzing workflow behind a manual workflow_dispatch trigger rather than running it on every push — long fuzzing sessions are expensive, and we’d rather run them deliberately than have them silently time out in CI.
Why bother, then
Because the alternative — not fuzzing untrusted parsers in a password manager — is worse. A .oxid file can arrive from a Git remote, a network share, or someone else’s laptop before it’s ever decrypted. An SSH key can be imported from an untrusted export. An audit log can, in theory, be tampered with by anyone who gets local file access. All three of these are parsed before any authentication happens. If any of those code paths panics on malformed input instead of returning a clean error, that’s a denial-of-service at minimum — and on some malformed-length-field bugs, potentially worse. Billions of fuzzer runs without a crash doesn’t mean the parsers are bulletproof. It means we’ve spent real compute looking for the cheap way in, and so far haven’t found one.
The next step on our credibility list is a professional third-party audit (we’re evaluating Trail of Bits, Kudelski, and NCC Group) — fuzzing is a floor, not a ceiling.
OxidVault is an offline-first, zero-knowledge password manager built in Rust and Tauri, designed for small and medium businesses that need on-premise credential management without cloud dependency. Vault files stay encrypted with AES-256-GCM end to end; parsing, encryption, and key derivation all run in the memory-safe Rust core. Read more about the architecture or see the fuzzing corpus on GitHub.
This article describes local fuzzing results as of July 2026, run and reported by the OxidVault team — not an independent third-party audit. Reproduction instructions are in fuzz/README.md in the repository.