At some point most small teams end up with the same secrets problem. You need API keys, database passwords and app config in more than one place. You also need more than one person to be able to change them. And you really don’t want the answer to be a plaintext .env living in somebody’s home directory, or a Slack DM from six months ago that still has the old production password in it.
The industry answer is usually HashiCorp Vault, or one of the hosted things that look a lot like Vault.
Vault is fine software. I’ve used it. For a lot of organisations it’s absolutely the right tool. It just also comes with a cluster, unseal keys, policies, auth backends, agents, lease renewal and enough operational surface area that “we should put the secrets somewhere proper” quietly turns into a second infrastructure project.
For a small team, that trade often isn’t worth it.
I didn’t need a secrets platform. I needed encrypted files that behaved predictably.
The bits that actually annoyed me
We already had a reasonable cryptographic answer. SOPS and age are boring in the good way. Encrypt the file, commit the ciphertext, decrypt it where the application actually needs the values. No server. No dashboard. No lease to renew at 3am.
What wasn’t boring was everything around that.
File layouts drifted. One repo put secrets in secrets/, another used config/encrypted/, another had a .sops.yaml that only half the team understood. People would create a plaintext starter file, encrypt it later, and leave the unencrypted copy sitting around longer than anyone intended. Git commands staged more than they meant to. Deploy machines needed the SOPS binary, the age binary, the right config and a shared understanding of which path meant what, just to turn a sealed file into something an application could use.
None of that is SOPS’ fault. SOPS encrypts files. It does not care whether your team has a consistent workflow for creating them, editing them, committing them or rendering them at deploy time.
That gap is where the mistakes live.
So I built a thin CLI around it
Keyseal started as an attempt to make that workflow boring.
Not to invent new cryptography. Not to replace Vault. Not to become another secrets-as-a-service product with a web UI and a pricing page. Just a small Go CLI that standardises the bits around SOPS, age and Git that teams keep reinventing badly.
SOPS still does the encryption. Age still holds the keys. Git still stores the history. Keyseal sits in the middle and makes the operations around those things harder to get wrong.
You talk in logical names like production/platform/app, not file paths. Editing goes through the normal SOPS flow. Read-only paths use the official SOPS Go decrypt library, so a CI or deploy host running render, exec, doctor or verify needs the Keyseal binary, the encrypted repository and age key material - not a pile of extra tooling.
One specific thing that bothered me enough to encode into the tool was the create path. It is extremely easy to scaffold a new secret by writing plaintext to the final path, encrypting it afterwards and hoping everybody cleans up. Keyseal’s add flow encrypts immediately. Plaintext starter content never lands where the ciphertext is supposed to live.
The cryptography can be perfect and the workflow can still leak the secret.
This turns out to be useful with coding agents too
I didn’t originally build Keyseal for LLMs, but the same boundary is surprisingly useful once you’ve got coding agents poking around repositories.
An agent can see the encrypted files in Git. It can talk about production/platform/app. It can open a PR that changes ciphertext. What it does not need, in normal use, is the actual secret values sitting in its context window.
When something actually needs credentials - a migration, a test suite, a deploy step - you keep the decryption at the process boundary:
keyseal exec production/platform/app -- php artisan migrateexec decrypts with the SOPS Go library, merges the env documents, overlays those values onto the child process environment and runs the command. It does not write a plaintext file. The secrets exist in that subprocess’s environment; they do not have to travel through the chat transcript for the agent to get useful work done.
render --stdout back into its own context. The boundary is the process you choose to launch.I’m not going to pretend this makes agents magically incapable of seeing secrets. If you give one a shell and it runs env, or you bounce render --stdout straight into the transcript, congratulations: the model has the values. Keyseal is not an access-control system and it does not invent new OS protections.
What it does give you is a flexible way to keep the default path honest. The repository stays ciphertext. The agent works with names. The command that needs credentials gets them injected at runtime. That is a much nicer shape than pasting production passwords into prompts, or checking a plaintext .env into a worktree the model can freely read.
What Keyseal is deliberately not
This is probably the most important part of the design.
Keyseal is not a Vault replacement. It is not a hosted secret service. It is not a daemon, a Kubernetes controller or a web UI. It does not implement its own cryptography.
If you need dynamic credentials, lease renewal, identity-based access across hundreds of services, or a centrally administered policy engine, you should use something built for that. Vault exists for a reason.
What Keyseal is for is the much more common small-team case: a handful of environments, a Git repository you already trust for code, age keys you can distribute carefully, and a desire for encrypted secrets that behave like ordinary repository objects until the moment something actually needs to run.
doctor tells you what’s wrong with the repository. verify turns those checks into a strict CI failure before production gets involved. Secrets problems are often workflow problems wearing a cryptography costume, and the earlier you make a broken workflow loud, the less exciting your deploys become.
So do you need Vault?
Sometimes, yes.
If you’re operating at a scale where secrets need to be issued dynamically, rotated automatically, tightly scoped per identity and audited through a central control plane, a real secrets system earns its keep. Standing up that complexity because a blog post said encrypted files are “not enterprise enough” is a different mistake.
For a lot of small teams, though, the honest requirement is much narrower. Encrypt the values. Keep them in Git. Make the create/edit/commit path hard to screw up. Decrypt them only at the boundary where the process runs. Validate the repository before deploy. Don’t invent a new crypto stack while you’re doing it.
That’s pretty much why Keyseal exists.
I don’t need to trust a vault cluster. I need to trust the workflow around the ciphertext.
The source, releases and docs are on GitHub if you want to poke at it.