uBix Vault, Beta 11 — The Last Gate I Can Build Myself
Unseal It With a KMS — Without a KMS Library
A running secrets manager has to solve a chicken-and-egg problem: it encrypts everything with a master key, but that master key has to come from somewhere at startup. The gold-standard answer is a cloud KMS or an HSM — a hardware or cloud service that holds the wrapping key and never hands it out, so the key that unlocks your vault never sits on the vault’s own disk.
uBix Vault could already do a version of this via its transit seal (unseal by asking another Vault-compatible engine to unwrap the key). Beta 11 covers the case most organizations actually have: AWS KMS, GCP Cloud KMS, Azure Key Vault, or a PKCS#11 HSM.
The obvious way to add that is to import the cloud providers’ SDKs — which is exactly what I wasn’t willing to do. uBix Vault has had one third-party dependency since the beginning; three heavyweight cloud SDKs in the security-critical seal path would end that, and it would put the provider-specific code in the one place I least want surprises.
So the seal doesn’t talk to any KMS. It talks to a command you provide:
your-command wrap # master key on stdin -> wrapped blob on stdout
your-command unwrap # wrapped blob on stdin -> master key on stdout
The vault pipes its 32-byte master key to that command and stores whatever comes back. Your command is the five-line script that calls aws kms encrypt, or gcloud kms, or pkcs11-tool. The KMS logic — and its credentials — live there, in a process you already trust, not in the vault. It’s the same idea as the auth methods a few betas back (verify a JWT without a JWT library) and Transit before that (sign without a signing library), applied one more time: reach any KMS without a KMS library.
It fails safe, the way a seal must: if the command errors or times out, the vault stays sealed. It never falls back to an unprotected key. And because a KMS-wrapped blob is opaque, the vault imposes no format on it — an AWS CiphertextBlob, a PKCS#11 result, anything round-trips as long as your command is consistent with itself.
I verified the whole path end to end: start the server pointed at a wrap command, initialize (the master key gets wrapped and stored), kill it, start it again — and it comes back unsealed, because the command unwrapped the key. No human, no shares, no KEK on the box.
Everything That Quietly Got Harder to Break
Between the last post and this one, a lot of the work wasn’t a feature at all — it was making the existing code harder to break, which is the part that actually matters for something guarding secrets:
- The parsers got fuzzed. Every place that reads untrusted input — ACL policies, JWTs, JWKS documents, transit ciphertext, snapshot files — now has a fuzz target that runs on every build. Millions of random inputs, no crashes.
- The crypto core got property tests. Shamir split/combine now has to round-trip for any secret and any valid threshold, not just the hand-picked cases; the barrier has to round-trip and stay encrypted-at-rest across hundreds of random keys and values.
- A real bug fell out of it. The crash-recovery testing found that if the vault died mid-write, the leftover temporary file could show up as a phantom entry in a backup. That’s fixed — and it’s exactly the kind of thing you want a test to find instead of a restore.
- The releases got signed. Every published image is now keyless-signed with cosign and carries an SBOM attestation, so anyone can verify an image is a genuine, unmodified release and see exactly what’s in it. (Getting that right took one more fix this week — the signing job needed to stop trying to attach the bill of materials to the GitHub release, which it didn’t have permission to do. Signed and attested now, cleanly.)
None of it moved the dependency count. Still one.
The Gate That Isn’t Mine to Build
Here’s the milestone, stated plainly: with beta 11, every engineering gate on my road to a real 1.0 has landed. Durable storage — done. The hardening pass — done. The KMS/HSM seal — done. I wrote the list of what stood between this project and being something you’d actually run, months ago, and I’ve now built all of it except one item.
The exception is the one that was never mine to build: an external security review. I can write careful code, fuzz the parsers, property-test the crypto, sign the releases — and I have. But I cannot audit my own cryptography and have that mean anything. The entire value of a review is that someone who isn’t me, and isn’t invested in my being right, looks hard and tries to break it. Until that happens, the disclaimer at the top of every one of these posts stands, and I keep running this alongside things I can afford to be wrong about, not in front of them.
That’s a strange and honest place to land a project: feature-complete and hardened, and still not done — because “done” for a secrets manager includes a judgment I’m not allowed to make about my own work.
Where It Is
uBix Vault is BSD 3-Clause, on GitHub at github.com/cwolsen7905/uBixVault, tagged v0.2.0-beta.11, with signed images and an honest roadmap that now has exactly one unchecked box on the path to 1.0.
Eleven betas ago it was a plan. It became a design, a binary, a service, a service that could authenticate anything and be its own CA, one whose data outlives its machine — and now one that can be unsealed by hardware it never touches. The engineering is, by my own list, finished. The last gate is a door I have to ask someone else to walk through.