I work with developers. That means at some point, someone needs to share a database password or API key quickly, and Slack is just… there. It's convenient. But that credential is now sitting in Slack's servers, searchable, forever. There are tools out there to handle this better, but many of them are server-side only, which means the people running the service can technically read what you sent.
So I built SecretVoid. And the core feature is this: I genuinely cannot read your secrets. Not because of a policy or a promise, but because of how the encryption works.
Here's how I got there.
The problem with many secret sharing tools
Many tools encrypt your secret on the server. You trust them not to look. That's fine for most use cases, but if you're sharing production credentials or API keys, "trust me bro" isn't good enough.
The alternative is client-side encryption. Your browser encrypts the secret before it ever leaves your device. The server only ever sees gibberish. I'd seen this technique used before, but I wanted to take it a step further.
Dual-layer encryption
SecretVoid uses two completely independent layers:
Layer 1 (client-side): Your browser generates a random AES-256-GCM key. It encrypts your secret locally. The key never leaves your browser. It goes into the URL fragment (#key), which the HTTP spec guarantees is never transmitted to the server. The server receives an encrypted blob it cannot read.
Layer 2 (server-side): When the server receives that blob, it encrypts it again with its own key before storing it. So what's sitting in the database is doubly encrypted.
To decrypt a secret you need both keys simultaneously: the one in the URL fragment, and the server key. A database breach alone gets you nothing. A URL leak alone gets you nothing either.
The URL fragment trick
This is the bit I found most elegant. When you share a secret, the URL looks like:
https://secretvoid.com/secret/abc123#clientKey
Everything after # is the fragment. This isn't a browser quirk or a convention. It's defined in the internet standards that all browsers must follow. RFC 3986 (the specification that defines how URLs work) states that the fragment is stripped from the URL before the browser makes a request. RFC 7230 (the HTTP/1.1 specification) makes it explicit: clients must not send the fragment to the server.
In plain terms: the part of the URL after # never leaves your device. The server receives /secret/abc123 and nothing else. The key stays in the browser.
The Web Crypto API handles the actual crypto work. No dependencies, built into every modern browser.
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
I published the client-side module as an open source npm package (secretvoid-crypto) so the code that runs in your browser is publicly auditable. You can compare it directly against what's being served. Open DevTools, go to Network, find crypto.js.
Password protection
Sharing a link is fine for most situations, but sometimes you want an extra layer. Maybe you're sending it to someone over an insecure channel, or you just want to make sure that even if the link gets forwarded, it's useless without a password. So I added optional password protection. The recipient enters a password when they open the link, and without it they get nothing.
Remember the fragment trick from earlier, the key that lives after the # and never reaches the server? Password protection builds on top of that.
Without a password, the fragment contains the raw encryption key. With a password, it contains a locked version of that key instead. Your secret is still encrypted with a random key, but that key is itself encrypted using your password before it goes into the URL. The recipient has to enter the password to unlock the key, and then the key to decrypt the secret. Two padlocks.
The part I like most about this: I wouldn't even know what their password is. It never leaves the browser. It never touches the server. The only thing I store is a flag that says a password is required. That's it.
There's also a salt mixed into the process, random data that means two people using the same password still produce completely different keys. It also makes brute-forcing impractical, because an attacker has to repeat 100,000 rounds of processing for every single guess they make.
Proving the encryption works
One thing I wanted to add was a way for users to see what's actually being transmitted. If you open DevTools before creating a secret, you can watch the create-secret request and see the payload. It's encrypted gibberish, not your plaintext.
I went a step further and added a loading overlay that briefly shows the encrypted payload as it's being sent, then displays it in full on the confirmation page. It's a small thing but it makes the zero-knowledge claim tangible and helps build trust.
Key rotation
Server-side encryption keys rotate automatically every Sunday at 2am UTC via a cron job. I keep three key versions active simultaneously: current, previous week, and two weeks old. Secrets can live for up to seven days, so you need overlap to avoid a secret losing its decryption key mid-life. Three versions gives a 14-day window which covers all edge cases.
What it can't protect against
I try to be honest about this. If your device is compromised with malware, the secret gets captured before encryption happens. Same story on the recipient's end. No encryption tool protects against a fully compromised device. That's true of everything.
The stack
- Node.js / Express backend
- MongoDB with TTL indexes for automatic expiry (no cron needed for cleanup)
- Tailwind CSS
- Web Crypto API for client-side encryption
- SendGrid for optional read receipt emails
- Stripe for the Pro tier
I self-host all fonts and icons to avoid third-party requests. Got PageSpeed to 100% after that change.
Try it
secretvoid.com - free tier, no account needed for basic use. Pro tier adds 7-day expiry and read receipts with more to come.
The crypto module is on npm: npm install secretvoid-crypto
Interested in what I build next, want to provide feedback, or have questions? I am happy to chat in the comments.
If you like the product, you can find me on Product Hunt, any support and upvotes are most welcome.
Top comments (0)