one-time-secret is a self-hosted web app for sharing secrets via a link that can be read once (optional) and/or expires automatically.
The server stores only encrypted ciphertext in MongoDB. Encryption and decryption happen on the client.
-
You enter a secret (plain text or a JSON object) and optional settings.
-
The browser encrypts it using
AES-256-GCM. -
The app saves the encrypted payload (ciphertext) in MongoDB under a random
key. -
You get a share URL of the form:
/secret/{uniqueKey}:{decryptionKey}The
uniqueKeyis used to fetch the ciphertext from the server, and thedecryptionKeyis used by the browser to decrypt it.
- Client-side encryption (
AES-256-GCM) - Optional passphrase (used to derive the encryption key via
PBKDF2) - Burn-after-read (deletes the stored ciphertext after a successful read)
- Expiration date for secrets
- Localized routes via
/{locale}(i18n)
- Next.js (App Router)
- React
- MongoDB
- Jest (unit tests)
- Tailwind CSS
- Node.js (see
package.jsonengines/scripts if you pin versions in your environment) - A MongoDB instance (local or remote)
The app uses MONGODB_URI for database access.
-
In development, if
MONGODB_URIis not set, it defaults to:mongodb://root:password@localhost:27017/one-time-secret?authSource=admin -
In production,
MONGODB_URImust be set.
npm ciThis repository includes compose.yaml to run MongoDB locally:
docker compose up -dOptionally initialize indexes/collections (uses mongosh):
npm run db:initnpm run devOpen http://localhost:3000.
npm testPOST /api/v1/secret— store an encrypted secret with metadata (expirationDate,burnAfterRead, ...)GET /api/v1/secret/key/{key}— fetch the encrypted secret; ifburnAfterReadis enabled it is deleted after being read
- The share URL contains the decryption key as part of the path (
{uniqueKey}:{decryptionKey}). Treat the full URL as sensitive: it can end up in browser history, logs, analytics, referer headers, screenshots, etc. - The server never sees the plaintext secret, but it can see/store the ciphertext and metadata.
- If you set a passphrase, it is required to decrypt in the browser.
See LICENSE.