DEVOPS UTILITIES

DEVOPS UTILITIES / ALL TOOLS

One-Time Secret Sharing

Send a password, API token, or private note as a self-destructing link. The secret is encrypted in your browser, only the ciphertext is uploaded, and the link works exactly once before the stored copy is deleted.

28 of 65,536 encrypted bytes

Encryption happens in this browser. Only the ciphertext is uploaded, and the key stays in the link fragment.

ZERO-KNOWLEDGE DESIGN

How the Encryption Works

When you create a link, this page generates a fresh 256-bit AES-GCM key and a random 12-byte initialization vector, encrypts your text with the Web Crypto API, and joins the initialization vector and ciphertext into one base64url string. That string is the only thing uploaded. The key is exported separately and appended to the link after a #, so the finished URL carries the key while the request that created it did not.

Nothing is reused between secrets. Every link has its own key and its own initialization vector, so recovering one secret tells an attacker nothing about any other.

DATA BOUNDARY

What the Service Receives

The distinction that matters is not a promise about behaviour, it is what the service is structurally capable of holding. Without the key, the stored bytes are not decryptable by the operator, by anyone who compromises the store, or by anyone served a subpoena for it.

What leaves the browser and what does not
ValueSent to the serviceReason
The plaintext secretNeverIt is encrypted in this browser before any request is made.
The encryption keyNeverIt is placed after the # in the link, and browsers do not send fragments to servers.
The ciphertextYesStored as opaque bytes the service has no way to read.
The chosen lifetimeYesNeeded so the record can expire on its own.
The secret identifierYesA random handle used to look the record up once.

WHY THE HASH MATTERS

Why the Key Sits After the #

Everything after the # in a URL is the fragment. It is a client-side addressing feature, and browsers strip it before building the HTTP request, so it never appears in a request line, a proxy log, an access log, or a referrer header. Putting the key there is what makes the key travel with the link while staying invisible to the server.

It is also the reason a truncated link is fatal. If a chat client, mail scanner, or copy-and-paste drops the part after the #, the ciphertext still exists but no key does, and no one can recover it.

SELF-HOSTED BACKEND

Where the Ciphertext Lives

The storage service runs on Contabo K3s rather than a managed provider. DNS for api.gajan.dev is proxied through Cloudflare, TLS terminates at Traefik with cert-manager, and records live in memory-only Valkey. Ciphertext is never written to disk, so there is no file, snapshot, or backup to recover it from later.

That is a deliberate trade. It means an unread secret does not survive a restart of the store, and it means occasional downtime on self-hosted hardware is normal. When the service is unreachable, nothing is lost that was ever readable.

EXACTLY ONCE

One Reader Wins, Every Time

Retrieval is a single atomic operation: the record is fetched and removed together, not read first and deleted afterwards. If two requests arrive at the same instant, one of them returns the ciphertext and the other returns the same not-found response as a link that never existed.

Not-found is deliberately indistinguishable across three cases: the secret never existed, it was already opened, or it expired. Distinguishing them would let someone probe which identifiers were real.

This page also never fetches a secret on load. The reveal page requires an explicit click, so a link preview, a security scanner, or a browser prefetching the URL cannot consume the secret before the recipient reads it.

LIFETIME

Expiry Behaviour

Every secret carries a lifetime chosen at creation: 1 hour, 24 hours, or 7 days. The record is removed when that lifetime elapses even if nobody opened it, so an abandoned link becomes a dead link rather than a credential sitting in a queue indefinitely.

Choose the shortest window the recipient can realistically work with. A one-hour link that is resent is safer than a seven-day link that sits unopened in an inbox over a weekend.

HONEST LIMITS

What This Does Not Protect Against

Encryption in the browser removes one specific risk, which is the storage service reading your secret. It does not remove the rest:

  • A compromised endpoint. If either machine has malware, a keylogger, or a hostile browser extension, the plaintext is readable at the moment it is typed or revealed. Client-side encryption cannot help there.
  • Whoever holds the link holds the secret. There is no identity check. The link is the credential, so anyone who obtains it before the intended recipient can open it, and you will only discover this because the recipient reports a dead link.
  • The recipient after they read it. They can screenshot it, paste it somewhere permanent, or leave it on screen. Destroying the stored copy does nothing about copies made afterwards.
  • Shoulder surfing and casual exposure. A revealed secret sits in a browser window like any other text, and a link on a shared screen or over someone's shoulder is enough.
  • The channel you use to send the link. Sending it over the same compromised channel you were trying to avoid gains you very little.
  • Availability. This is homelab infrastructure. Treat it as a convenience, not as something to depend on during an incident.

Used well, this is a way to hand one credential to one person and have the copy disappear afterwards. Rotate anything you share this way once it has served its purpose.

FREQUENTLY ASKED QUESTIONS

One-Time Secret FAQ

What is a one-time secret link?

It is a URL that reveals a secret to the first person who opens it and then destroys the stored copy. It suits credentials that must reach one person and should not sit in a chat history or inbox afterwards.

Can the server read my secret?

No. The secret is encrypted in your browser with AES-GCM before the request is sent, and the key is never transmitted. The service holds ciphertext it has no key for.

Why is the decryption key after the # in the link?

Everything after the # is the URL fragment. Browsers keep fragments local and never place them in the HTTP request, so the key can travel inside the link without ever reaching the service that stores the ciphertext.

What happens if two people open the same link?

Only the first reader gets the secret. Retrieval and deletion happen as one atomic operation, so exactly one request can win; everyone after that sees the same not-found response as an expired or never-existing link.

How long does a secret last if nobody opens it?

You choose 1 hour, 24 hours, or 7 days. When that time passes the record is removed whether or not it was read, so an unopened link simply stops working.

Why did my link stop working before the recipient opened it?

Something opened it first. Link previews, mail security scanners, and archiving tools all follow URLs. The reveal page never retrieves a secret automatically, so merely loading the link does not consume it, but any tool that goes on to perform the reveal request would.

What happens if the link is truncated when it is pasted?

The fragment is the key, so a link cut short after the # is unrecoverable. There is no copy of the key anywhere else. Create a new secret and send the complete link.

Where is the ciphertext stored?

On a Contabo VPS running a self-hosted K3s cluster at api.gajan.dev, with Cloudflare DNS and proxy in front and TLS terminated at Traefik via cert-manager. Ciphertext sits in a memory-only Valkey store, is never written to disk, and disappears on read, on expiry, or if the store restarts.

Is this a replacement for a password manager?

No. It is a transport for handing a credential to one person once. Long-lived credentials belong in a password manager or a secrets manager, and anything shared this way should still be rotated afterwards.