Recursivity
All projects
Open source

Shh

Self-destructing secret sharing, hosted on your own hardware

View on GitHub
LanguageTypeScript
InterfaceWeb, PWA, HTTP API
DeployDocker or Raspberry Pi
LicenseMIT
  • Composing a secret: choose when it dies, optionally gate it behind a password, and the key never leaves the URL fragment.
    Composing a secret: choose when it dies, optionally gate it behind a password, and the key never leaves the URL fragment.

Shh is one-time secret sharing you run yourself. Paste a password, pick an expiry, and you get back a private URL. The recipient opens it, clicks to reveal, and — if you chose first_view — the secret is deleted in the same transaction that decrypted it.

The server never holds the key

On create, the server generates a random AES-256-GCM key, encrypts the plaintext, and stores only the ciphertext in SQLite. The key is never persisted. It goes into the URL fragment — the part after the # — which browsers do not transmit, so it never lands in a request log, a proxy log, or an access record. On reveal, the client sends the key back in the request body to decrypt.

That single design decision is what makes the threat model tractable, so the README states it plainly rather than gesturing at “military-grade encryption”:

A versioned API, because clients exist

There is a versioned HTTP API at /api/v1 for programmatic clients — a CLI, a script, or the Omarchy bar widget built against it. Unlike the browser route, create returns a complete shareable URL: a desktop client has no window.location, and an instance behind a proxy cannot infer its own public origin.

Because anyone can self-host it, a client is required to treat the server address as user configuration and validate it against /api/v1/info rather than hardcoding one. Auth is optional and off by default; setting SHH_API_TOKENS requires a bearer token on /api/v1 while leaving the browser routes open, since the web UI has nowhere to hide a token.

Stack

FrameworkNext.js 16 with TypeScript, installable as a PWA
CryptoAES-256-GCM per secret; bcrypt (12 rounds) for optional passwords
StorageSQLite — ciphertext only, with a sweeper that runs every minute
Limits100 KB plaintext; 10 reveal attempts per IP+id per 5 min; 60 creates per IP per hour
Proxy awarenessSHH_TRUSTED_PROXY_HOPS, so rate limits key to the real client rather than a forgeable header
DeployMulti-arch GHCR images (amd64, arm64), or a systemd + nginx deploy to a Pi