Secrets Have Nowhere to Go — The Birth of Vault · Vault Series 1/3
One evening I had to send a signed APK to a test user. WhatsApp corrupted the file, email stopped at 25 MB, and I ended up uploading to WeTransfer and dropping the link in Slack. How long would that link be in someone's hands, who had downloaded it, how could I revoke it once they had — there was no answer to any of these. That moment I thought: why isn't there a place I can put a sensitive file where control stays with me?
This is the story of how I started writing Vault. First in a three-post series. There's no code here; just how a discomfort turned into a decision, and a decision into a piece of infrastructure.
Where do you even put a file?
The story is boringly ordinary. I had to send a signed APK to a test user. First WhatsApp — I sent it as a "document," and the other side said it wouldn't open, because WhatsApp had compressed it. I tried email and ran into the 25 MB limit. Finally, the classic move: upload to WeTransfer, copy the link, paste it in Slack.
The link went out, the file got downloaded, the job got done. But that night a question lingered:
- Whose hands is that link in right now?
- How many times has it been downloaded?
- If tomorrow I say "pull back this build," how do I revoke the link?
- Does WeTransfer store the file encrypted on its disk, for how long?
I had no answers. I had handed off a secret — because a signed build is a secret — through a channel outside my control, to a server outside my control. And that was the tenth thing I'd done that week. A .env, a customer report, a database backup, a set of API keys… all making the same trip: wrong channel, wrong destination.
The strange life of what we call a "secret"
Stopping for a second, I noticed that sensitive files aren't actually a first-class citizen of any tool.
- Email was designed for text; attachments came later.
- Slack/WhatsApp exist for instant messaging; files are a byproduct.
- WeTransfer/Dropbox exist for the link, but they don't care about the fact that the link is a secret — if it leaks, the file leaks.
- S3 presigned URLs are closer, but they're a raw primitive; you still have to layer identity, authorization, auditing, and revocation on top.
None of them put this simple sentence at their center: "This file is a secret, and I should decide where it goes, for how long, how many times."
The first instinct, of course, is "are you going to write yet another one — just use something off the shelf." But when I asked the question from the other side, the answer became clear: none of the off-the-shelf tools gave me control. They gave me a convenience, and in exchange they took ownership. When the thing in question is a secret, that's the wrong trade.
Why not a ready-made service?
I wanted three things at the same time, and no tool on the market gave me all three under my ownership:
- Encrypted at rest. Even if the server were stolen, or a disk image leaked, the files should be unreadable. The encryption key should belong to me, not the provider.
- I decide who, what, and with what role. Project-based. One person sees project A's files, not project B's.
- Everything leaves a trace, and everything is revocable. Who downloaded what, when a link was opened, when a file was deleted — all recorded. And a link should die the moment I want it to.
When those three come together, what emerges isn't a "file upload tool." What emerges is a trust boundary: an enclosed area where secrets live, and which talks to the outside only on rules I set. The reason it's self-hosted isn't stubbornness — it's that the owner of the trust boundary has to be me.
Three pillars: encrypted, authorized, auditable
When I sat down to write Vault, I didn't have a feature list. I had three pillars:
- Encrypted → Files are encrypted with AES-256-GCM before being written to MinIO (S3-compatible storage). With no key they're written in plaintext, but with a key no plaintext byte ever hits the disk.
- Authorized → Users, projects, roles. A file belongs to a project; a user is attached to a project with a role. Access flows out of that triangle.
- Auditable → Every meaningful action leaves an audit record. Every share link is finite — by time, by download count, or by a command from me.
What I find interesting: once I accepted those three pillars, most subsequent decisions slotted into place by themselves. "What happens when a file is deleted?" → think about what happens to the encrypted object and the audit record. "What if the same filename gets uploaded again?" → same authorization, but the version should be new. "What when a link expires?" → should the file really go away, or just the link? Every question leaned on one of the three pillars.
"Sharing" isn't a feature, it's infrastructure
I want to underline a point here, because it's easy to skip. Most people think of file sharing as a feature — a "share" button in the corner of an app. I started seeing it as an infrastructure layer.
The analogy: 20 years ago every application set up its own user table, its own session management, badly. Then "identity" was accepted as a separate layer. Today secure file sharing for sensitive content is in the same place: every team is doing it inside their own app, with email and WeTransfer patches, untraceable and irreversible. What Vault tries to do is pull that layer out — turn the safe relocation of a secret into a shared piece of infrastructure that every app doesn't have to reinvent.
Once that mental model clicks, Vault is no longer "my file vault." It's a shared gate reachable from the CLI, the web UI, and a tool an AI agent calls. Humans and machines pass through the same boundary, subject to the same rules.
A one-sentence manifesto
When I started on Vault I wrote myself a sentence; it's still there, and it still protects me from bad decisions:
"Vault is the place where a secret can move, under control."
Four words in that sentence carry the weight:
- Secret → not just a file; something worth protecting. Encryption flows from here.
- Under control → who, how much, how many times. Authorization and auditing flow from here.
- Can move → not a static archive; something shared, downloaded, in motion. Streaming and share links flow from here.
- Place → a single boundary, with a clear owner. Self-hosting flows from here.
When I'd want to add a feature, I'd consult that sentence. "Does this serve the controlled movement of a secret?" If not, I didn't add it. I can't tell you how much pointless work that simple filter has saved me.
Are you making the same journey?
If you've read this far, you've probably had your own signed build, customer report, or set of credentials slip through the wrong channel last week. The fix doesn't have to be writing your own Vault. But it's worth sitting down and asking:
"Who holds control over the secrets I send — me, or the channel I use?"
If the answer is "the channel," you've unknowingly handed off ownership. If you want the answer to be "me," then you probably need a trust boundary too.
In the next post we'll go into Vault's core: why I picked only Go, PostgreSQL, and MinIO instead of an exotic stack; how a 600 MB file streams through without ever landing in RAM; and what AES-256-GCM encryption is actually protecting when it chunks a file into 64 KB pieces.
But I want to end this post with this sentence, because it captures Vault's spirit best: a secret should go where it goes on rules you set — or not go at all.