← All churts

uBix Vault — Building a Secrets Manager From Scratch

uBix Vault — Building a Secrets Manager From Scratch

Starting Something New (and Saying So Honestly)

This is a design-notes post, not an announcement. uBix Vault is in early development — planning and scaffolding, not production-ready. If you need secrets management in production today, use HashiCorp Vault or its fully-open fork OpenBao. I want to be upfront about that before I say another word, because a secrets manager is exactly the kind of software where overselling maturity is how people get burned.

With that said: I’m building one from scratch, in Go, and I think the why and the early design decisions are worth writing down while they’re fresh.

What It Is

uBix Vault is a from-scratch secrets manager in the shape of HashiCorp Vault — the central authority that stores, generates, and controls access to the sensitive material an application needs: API keys, database credentials, certificates, encryption keys. The plan is a Vault-API-compatible HTTP interface, so existing Vault client libraries, SDKs, and Terraform providers work against it without new tooling.

The core capabilities I’m targeting for v1.0:

  • Encryption barrier — everything at rest is AES-256-GCM encrypted; the storage backend never sees plaintext.
  • Seal / unseal — the vault boots sealed. The master key is reconstructed from Shamir k-of-n key shares before a single secret is readable. No one person holds the whole key.
  • Token auth + ACL policies — default-deny, path-based capabilities.
  • KV v2 — versioned static secrets.
  • Transit — encryption-as-a-service: apps encrypt, decrypt, sign, and HMAC over the API, and the key never leaves the vault.
  • Dynamic secrets — short-lived database credentials generated on demand and auto-revoked on lease expiry, behind a pluggable DatabasePlugin interface (MariaDB is the reference).
  • Audit device — every request logged with sensitive values HMAC’d, fail-closed.

Why Build This When Vault and OpenBao Exist

This is the first question anyone should ask, and I asked it of myself. Secrets management is a mature space with excellent implementations. OpenBao in particular is a drop-in, actively-maintained, fully-open Vault — for a production deployment that just needs an open Vault, it’s the right choice, full stop.

So uBix Vault isn’t trying to displace them. It exists for three honest reasons:

  1. First-class uBix ecosystem support, without coupling. It originated to provide secrets management for uBix Core, purpose-built to integrate cleanly there — while staying framework-agnostic, so any stack can use it over the HTTP API.
  2. Lightweight and opinionated. A small, focused core — the parts that matter most — rather than the full breadth and operational weight of Vault or OpenBao.
  3. Full design control, and the education that comes with it. Owning the barrier, the seal/unseal state machine, the policy engine, and the lease lifecycle end-to-end. There is no better way to actually understand how a secrets manager works than to build the security-critical parts yourself.

The scope philosophy is depth over breadth: a small, finished, tested, well-documented core — not a partial clone chasing feature parity. I’d rather ship something narrow that gets the hard parts right than something wide that gets them 80% right, which for security is the same as wrong.

The Language Decision: Go (and the Honest Caveat)

The most consequential early decision was the implementation language, and I wrote it down as an architecture decision record. The contenders were Go, Rust, and C++.

C++ was out first. For a security product, manual memory management adds an entire class of CVEs — buffer overflows, use-after-free — which is the exact opposite of what a secrets manager is supposed to sell. C++ only wins with a hard FIPS/HSM/embedded constraint and deep existing secure-C++ expertise on hand. Neither applies here.

Rust vs. Go was the real decision — and it’s a genuine trade-off, not a slam dunk. Rust is stronger on the one thing that actually matters for this domain and that Go is weak at: deterministic secret handling. Go’s garbage collector can copy and relocate memory, which means you cannot guarantee a secret is truly wiped and never duplicated in RAM. Rust, with no GC and explicit ownership, can.

I chose Go anyway, for reasons that outweigh that caveat for this project:

  • Ecosystem fit. HashiCorp Vault and the entire cloud-native secrets/infra world — Kubernetes, Consul, etcd, cert-manager, SPIFFE/SPIRE — are Go. Targeting wire-compatibility with Vault while reusing its ecosystem is dramatically easier in the same language.
  • Contributor pool. The people who can be productive in a secrets-management codebase overwhelmingly write Go.
  • Domain libraries + time-to-MVP. The crypto/* standard library, x/crypto, hashicorp/raft, gRPC, a trivial single-static-binary build, and a gRPC plugin model all favor Go decisively.

The honest part: HashiCorp itself lives with exactly this Go memory caveat — it mitigates with mlock and short secret lifetimes, and I take the same approach (mlock the process, minimize how long secrets sit in memory, zero buffers where the runtime allows). And I left an escape hatch: the crypto core sits behind an interface, so if an extreme threat model — nation-state memory forensics, FIPS 140-3 physical — ever becomes a first-order requirement, a Rust or C++ module can be swapped in without rewriting the system.

That’s the kind of decision I most enjoy: no free lunch, a real weakness accepted with eyes open, and a documented path out if the weakness ever becomes the thing that matters.

The Architecture, So Far

uBix Vault is layered, and everything rests on the barrier; the barrier rests on the storage backend:

  clients → │  HTTP API  ·  CLI (ubixvault)                │
            │  Auth Methods  →  Token & Lease Manager      │
            │  Policy (ACL) Engine  ·  Identity            │
            │  Secrets Engines (KV v2, Transit, DB dynamic)│
            │  Barrier (AES-256-GCM)                       │
            │  Storage backend (file · in-memory)          │

The design goals I’m holding myself to: secure by default (default-deny, encrypted at rest, audited, memory-safe), Vault-API-compatible at the wire level, a single static binary with no external dependencies to run a node, pluggable everywhere (storage, auth, secrets engines, audit are all interfaces), and operationally predictable.

The one design choice I want to call out is including Transit and one dynamic database engine in the MVP rather than deferring them. They’re the two capabilities that most distinguish a real secrets manager from a glorified encrypted key-value store — encryption-as-a-service and the full dynamic-secret lease lifecycle. Shipping them in v1.0, even minimally, proves the whole model end-to-end. What I’m explicitly deferring is the heavy stuff: Raft HA and the full engine/auth matrix are months of work that would delay any shippable proof of the architecture, so they’re post-MVP extensions.

Where It Is, and What “Done” Means

Right now it’s scaffolding and design docs — the architecture, the decision records, the threat model, and a roadmap that separates committed core from optional extensions all live in the repo as first-class artifacts. That’s deliberate: for a security project, the reasoning is part of the deliverable.

The v1.0 definition of done is an honest one — an initialize → unseal → authenticate → use → audit flow that works end-to-end, with the crypto core and the seal/unseal state machine actually tested, CI green on every commit, and real docs. Finished and polished for a narrow scope, not broad and half-built.

It’s BSD 3-Clause licensed and on GitHub at github.com/cwolsen7905/uBixVault. It’s early — but the hard, interesting parts of a secrets manager are exactly the parts I’m starting with, and I’ll write more as the barrier and the seal/unseal machine come together.

← All churts