Designing a Zero-Knowledge Secret Manager · John Anthony Pecson
Home/ Blog/ Open Source

Designing a zero-knowledge secret manager

How I architected MeowPass so the server can never read your secrets, Argon2id, AES-256-GCM, and X25519 key exchange, without a single plaintext byte leaving the device.

John Anthony Pecson
John Anthony Pecson
Mar 4, 2026 · 14 min read
Zero-knowledge architecture cover

Every engineering team I've joined has the same dirty secret: the secrets themselves. API keys live in Slack DMs, a .env file someone pasted into Notion two years ago, and the muscle memory of one teammate who has since left. I built MeowPass because I was tired of being that team, and because I wanted a vault I could trust even if I didn't trust the server it ran on.

This is the architecture that makes that possible: a zero-knowledge design where the server stores your secrets but can never read them. Here's how the pieces fit together.

The problem with .env files

A .env file is a plaintext liability that you've decided to feel okay about. It works because it's simple, and it fails for exactly the same reason. There's no access control, no audit trail, no rotation, and no answer to the question every new hire asks on day one: "where do I get the secrets?"

The usual fixes trade one problem for another. Cloud secret managers solve access control but bury you in IAM setup and SDK calls. Committing encrypted .env files to git solves distribution but couples your secrets to a single shared key. I wanted something with the ergonomics of .env and the security of a real vault.

What zero-knowledge actually means

"Zero-knowledge" gets thrown around loosely, so let me be precise about the property MeowPass guarantees: the server never possesses enough information to decrypt your secrets. Not your master password, not the derived key, only ciphertext it cannot open.

The test that matters
If our entire database leaked tomorrow, an attacker should walk away with nothing but meaningless bytes. That single constraint shapes every decision below.

Deriving the key on-device

It starts with your master password, which never leaves your machine. We derive an encryption key from it using Argon2id, the same memory-hard function that protects 1Password vaults, tuned to be expensive for attackers and tolerable for you.

deriveKey.ts
const key = await argon2id({
  password: master,        // never sent anywhere
  salt,                    // per-user, public
  memoryCost: 19456,       // 19 MB
  timeCost: 2,
  parallelism: 1,
  hashLength: 32,
});

The salt is public and per-user; the password is the only secret, and it stays local. The output is a 256-bit key that exists only in memory on your device.

Sealing secrets with AES-256-GCM

With a key in hand, each secret is encrypted with AES-256-GCM before it touches the network. GCM gives us authenticated encryption, confidentiality and tamper detection, so a modified ciphertext fails loudly instead of silently returning garbage.

seal.ts
function seal(key, plaintext) {
  const iv = randomBytes(12);          // fresh per secret
  const { ct, tag } = aesGcmEncrypt(key, iv, plaintext);
  return { iv, ct, tag };       // only this leaves the device
}

Sharing without sharing keys

Team sharing is where most "encrypt your .env" tools quietly give up and pass around a single private key. MeowPass uses X25519 key exchange instead: each member has their own keypair, and a vault key is wrapped individually for every recipient. No raw key ever travels between people.

Owner
Wraps vault key with member's public key
Server
Stores wrapped keys, opens none
Member
Unwraps with their private key, locally

Revoking access is just deleting that member's wrapped key and rotating, one command, mp rotate, re-seals everything.

How it compares

MeowPass Encrypted .env Cloud KMS
Zero-knowledgeYesNoNo
Team sharingX25519 per memberShared keyIAM policies
Setup time2 min1 min30+ min

What I'd do differently

If I were starting again, I'd design the audit log as an append-only structure from day one rather than retrofitting it, and I'd ship the hardware-key unlock path earlier, passwords are the weakest link in any otherwise-strong system.

But the core bet has held up: make the secure path the easy path. Engineers don't adopt security tools because they're secure; they adopt them because mp pull is faster than asking in Slack. Get the ergonomics right and the encryption gets to do its job.

Takeaway
Zero-knowledge isn't a feature you bolt on, it's a constraint you design backwards from. Decide what the server is allowed to know, then build everything else around that line.
← Previous
Scaling WebSockets to 50K concurrent users
Next →
Designing MCP tools AI agents won't misuse

Related articles

{{ r.cat }} · {{ r.read }}

{{ r.title }}