Clipboard Sharing Online

Security and threat model

What the server and a database thief can see, what the design guarantees, and where it is weak — condensed from docs/SECURITY.md in the repository.

The claim, stated precisely

“Encrypted” on most clipboard sites means TLS: the text is protected on the wire, then decrypted by the operator's server, which can read, log, retain or leak it. Our claim is stronger: the server receives only ciphertext and never any key or password, so it has nothing to decrypt with. TLS (with HSTS) remains, as a second layer.

Construction

  • Argon2id(password, APP_SALT), m = 64 MiB, t = 3, p = 1, 32-byte output, in the browser via WebAssembly.
  • HKDF-SHA-256 with distinct info strings splits that into a 16-byte room_id (sent) and a non-extractable AES-GCM-256 CryptoKey (never sent).
  • Each push: a random 96-bit IV from crypto.getRandomValues; the upload is {roomId, ciphertext, iv}.
  • Membership: a 256-bit random bearer token, returned once, held in memory only; the server stores its SHA-256.

What the server sees

Sees: room ids; ciphertext and IVs; ciphertext size and timing; created and expiry timestamps; each room's capacity, sealed flag, sync mode and expiry duration; member rows of id, role, token hash and join time. Client IPs are counted in memory by the rate limiter and never written to the database.

Never sees: the password, the Argon2id output, the content key or the plaintext — including in live rooms, where the WebSocket frames carry the same ciphertext the database stores.

An attacker with the entire database

They get the ciphertext of rooms alive at that moment — an hour at most. They do not get a key. They do get an offline guessing oracle: for each candidate password, run Argon2id and HKDF and check whether the derived room_id is in the table. Because the salt is fixed and public (two devices share nothing but the password, so there is no per-user salt to exchange), one guess is tested against every room at once, and a precomputed dictionary works against every future dump.

Argon2id makes each guess cost 64 MiB and three passes, and that is the whole defence. The built-in “Safer” generator gives roughly 100 bits (16 characters from 85 symbols) — out of reach. The default “Simple” one gives about 25 bits (four distinct letters, two distinct digits): fine against someone racing a sealed room in real time, not against an offline attacker with a GPU cluster. Token hashes are SHA-256 of 256-bit random values and grant nothing.

What sealing buys, and what it doesn't

A private room seals the moment it reaches capacity, and the flag is write-once. Legitimate devices join in seconds and cracking takes hours, so a password cracked or leaked later finds the room closed. It is access control, not cryptography: it does nothing for ciphertext already in a stolen database, lasts only as long as the room, and open rooms opt out of it.

Known limitations

  • Weak passwords lose — see above; nothing server-side fixes that.
  • You trust the JavaScript we serve. A hostile build could exfiltrate plaintext. The CSP limits egress to our origin and Cloudflare Web Analytics, whose beacon script also runs in the page. It is not an XSS backstop: script-src allows 'unsafe-inline', which Next.js's static runtime requires.
  • No freshness guarantee. GCM detects modification, but nothing binds a blob to a sequence number, so a malicious server could replay an older blob from the same room, or withhold it.
  • Same password, same room. Anyone who knows the password while the room lives can join an unsealed room, or claim its last slot and lock you out.
  • Self-lockout. Tokens live in memory, so a reload forfeits your slot in a sealed room.
  • Out of scope: compromised devices or browser extensions, traffic analysis (who talked to which room id, how much, when), and share links or passwords sent over insecure channels — a share link is the password.

Check it yourself

Read the full threat model and the code in the repository, then open your browser's Network tab while you use a room. The plain-language version is on the privacy page.