Show HN: Keeper – embedded secret store for Go (help me break it) (github.com)

by babawere 33 comments 64 points
Read article View on HN

33 comments

[−] Retr0id 35d ago
Mmmm vibecrypto, my favourite. I don't see anything obviously broken (at a glance) but as a perf improvement, there's little reason to use Argon2id for the "verification hash" step, might as well use sha256 there. There is also no need to use ConstantTimeCompare because the value being compared against is not secret, although it doesn't hurt.

The "Crash-safe rotation WAL" feature sounds sketchy and it's what I'd audit closely, if I was auditing closely.

[−] babawere 35d ago
Thanks for the look. On the verification hash, you're right, SHA256 would work there. Argon2id was overkill, I agree 100%.

The crash-safe WAL is the part I'm most nervous about too. That's exactly why I posted this. I want eyes on the rotation logic specifically.

And yeah, single bbolt db is a limitation. I could have used pebble or any other, but trade-off for simplicity (a single *.db). A true WAL will need external file. The storage is pluggable though also open to improvement.

Still very young.

[−] nonameiguess 35d ago
Keeper is already the name of a popular enterprise secrets store: https://docs.keeper.io/en/user-guides/web-vault

I haven't used it, don't advocate for it, and have no opinion on either its viability or your product's viability for any specific use case. Mostly I just think it's a bit confusing to have two separate products in a very similar space with the same name.

[−] babawere 35d ago
thanks for the update ... will definitely look for a better name
[−] modelorona 35d ago
Name could conflict with Keeper Security
[−] JoeBOFH 35d ago
Came to say the same thing lol. Especially since Keeper Security deal with credential management.
[−] babawere 35d ago
So have been told. Will definitely look for a better name
[−] emanuele-em 35d ago
Per-bucket DEKs with HKDF, hashed policy keys to kill enumeration, HMAC audit chain. This is the kind of boring-correct crypto design I rarely see in Go libraries. memguard for the master key is a nice touch too.
[−] babawere 35d ago
I was thinking its better to be boring-correct :)
[−] emanuele-em 35d ago
yes I totally agree, my message was a compliment :)
[−] ComputerGuru 35d ago
We actually just ported SecureStore to go, it’s sort of like this but with cross platform clis and intended to also allow sharing secrets across services and languages, in a secure and embedded fashion! It’s available in rust, php, .net, JS/TS, Python, and golang and easy to port to others.

I didn’t get a chance to do a write up but the golang port is here: https://github.com/neosmart/securestore-go

The approach to crypto is very different, we went with what’s very well understood and very well supported on all platforms with little or no dependencies (eg we can use web crypto in JS frontend or backend with no external libs or crypto JS library nonsense).

The original .NET and Rust code is from over a decade ago and carefully architected (well before vibecoding was a thing), the secrets are stored in a human readable (json) vault that can be embedded in your binaries or distributed alongside them and be decrypted with either password-based or key-based decryption (or both).

The rust repo has the most info: https://github.com/neosmart/securestore-rs

[−] blastslot 35d ago
That’s actually a pretty interesting tradeoff — especially going with “boring crypto” that’s widely supported vs pulling in heavier deps.

The JSON vault + cross-language portability is nice too, especially if you’re embedding secrets across services without tying yourself to one runtime. Curious how you handle key management at scale though — that’s usually where these systems get tricky more than the crypto itself.

[−] ComputerGuru 35d ago
SecureStore is an open spec/protocol for managing secrets in a secure and portable manner, while it defines the decryption key formats (currently: key-based, password-based, or a mix of both interchangeably) it doesn't get into the mechanics of key management, which are "trivial and left as an exercise for the reader."

More seriously though, you're supposed to use separate vaults (with the same keys, where "keys" is the name of the secrets, not the decryption keys) for testing/staging/production, e.g. perhaps secrets.{testing,production,staging}.json and the same secrets.{testing,production,staging}.key for the decryption keys, and store both the username and password in them (after all, it's just an encrypted, glorified KV store) so that you don't have to hard-code any usernames and conditionally load them based on the environment in your code (so db:username is one "secret" and db:password is another (actual) secret).

The secrets vaults (the secrets.json files) are non-sensitive and can be versioned and pushed to your server the same way you push the binaries. Now how you move the secrets to the server is up to you. You could do it the old-fashioned way and just have it as an environment variable, in which case even when your env vars leak at least you haven't leaked your api keys, only the key to decrypt them (which you'd then rotate), but that's not a recommended option. Ideally you'd instead use whatever secure channel you use to init/stage the servers to begin with to transfer the secure key files - the key files are generally immutable, even as the secrets change, so you only have to do this once (ideally via a high-friction, high-auth mechanism, for most people not at FAANG scale, probably manually).

You can also use whatever additional layer of abstraction on top of the symmetric SecureStore decryption key you like. For example, you could asymmetrically encrypt the keyfiles and then each host would decrypt it with its own private key, or have a secrets side channel that's just used to obtain the static decryption key over the local network, or use your operating system's encryption facilities to transmit it, whatever works for you at whatever point on the complexity/security curve you desire.

(These are all just options, none are official recommendations.)

[−] CharlesW 35d ago
From a project perspective, is this for fun or is it meant to be a production solution? If the latter, what problem(s) are you trying to solve that established solutions like fnox don't? https://github.com/jdx/fnox (I'm an fnox user who's unfamiliar with this space, and am curious what your critiques would be.)
[−] babawere 35d ago
Both, honestly. Fun and production intent. But production here is very specific, embedded in a single Go binary, a single *.db not a CLI tool (the cli you see there is just for inspection) for developer or CI.

The problem fnox solves is great, unified access to secrets across dev, CI, prod with cloud backends. That's a different layer of the stack.

Keeper solves a lower-level problem: you have a Go process (a load balancer, a control plane, a daemon) that needs to store secrets inside its own database not in a separate file, not in a cloud vault, not in env vars. Secrets that need per bucket isolation, audit trails, and crash-safe rotation.

Here is my thinking :

- fnox = how your CLI and deploy scripts get secrets

- Keeper = how your running binary stores secrets at rest

Different problems, Could I build Keeper on top of fnox? Probably. But then I'd have a file on disk with secrets that fnox manages which is exactly the problem I wanted to eliminate.

[−] elthor89 35d ago
I have been looking for something like this. I know openbao, hashicorp vault.

But they require to be placed on a separate server, and come with their own infra management.

Is the idea of this project to embed this into you app, instead of relying on .env or an external vault?

[−] babawere 35d ago
Honestly… the initial use case is to hide certs from the file system and secrets from the environment. However, this can be extended.

The primary issue has been not being able to manage an encrypted storage system… the main goal is to have something that can be audited, not just secured.

yes 100% ... embeded

[−] guyle 33d ago
Interesting approach for embedded use cases. Curious how you see this compared to the OIDC trend in CI/CD where the goal is eliminating stored secrets entirely — different problem space but related threat model.
[−] sneak 35d ago
I have a similar one called “secret”, also in Go, that is more CLI-focused and uses the filesystem as database.

https://git.eeqj.de/sneak/secret

[−] babawere 35d ago
Thanks for sharing this. secret looks really well thought out, the three-layer key hierarchy is impressive. And using age is a solid choice. once considered it.

Different trade-offs though, Keeper is library first embedded. secret does per version keys with symlink switching - nice, Keeper does per-bucket DEK isolation + audit chains. Both solve "encrypted local storage" but for different workflows.

I'll definitely be looking through your code for ideas

[−] n0n 35d ago
Genuine question: what's your thread model?

Vault gives time limited Tokens with Network Boundary. Instead of Keeper, i would just use age:

# write

echo "my secret" | age -r > secret.age

# read

age -d -i key.txt secret.age

[−] sneak 35d ago
https://git.eeqj.de/sneak/secret

This is an age+filesystem secrets manager that I made that is basically what you wrote, but with more organization.

[−] babawere 35d ago
not when you need an audit system
[−] tietjens 35d ago
Could I use this to store secrets to hide env vars from agents?
[−] cifer_security 35d ago
A few thoughts on the WAL approach: WAL for crash-safe rotation is tricky — if the write isn't atomic, you can end up in a corrupt state on crash. An append-only log might be safer. For "paranoid env" use cases, have you looked at post-quantum KEMs? ML-KEM is now NIST standardized and has better forward secrecy properties against quantum adversaries. What's your threat model for the WAL feature?
[−] RALaBarge 35d ago
Hey I ran this request through my AI harness (beigeboxoss.com), first with a smaller local model and then validated with Trinity Large via OR. https://github.com/agberohq/keeper/issues/2 -- YMMV but wanted something to do with my coffee, thanks!
[−] greatkhalid 26d ago
[dead]
[−] takahitoyoneda 35d ago
[dead]