cipher.ink Write a message

Learn · Technologies we use

Burn after reading: how it works here

What "deleted" means for cipher.ink exactly, why a link preview can't burn a message, what happens with two readers at once, and what no website can promise.

A burn-after-reading message is one the server deletes the instant it hands it to a reader. The first person to open the link gets the message, and everyone after gets "Burned." Below we lay out how cipher.ink does it, in the order things happen on the server, which parts of the promise are firm and which aren't, and what no website can promise however it's built. The promise covers the server's copy. It can't reach into a reader's screen.

What happens, step by step

A message sealed with Burn after reading has a flag set when it's stored. Its ciphertext sits in the database like any other, but the page the link serves is deliberately empty of it. Here's what the reader's browser and the server do when someone opens the link:

  1. The link loads a lock card. The server sends the page without the ciphertext. All the page knows is that a burn message with this id exists, plus the passphrase hint if there is one. Loading the page doesn't burn the message and doesn't count as a read.
  2. The reader presses "Unseal and burn". Only then does the page ask the server for the ciphertext, with a request called open.
  3. The server ends the message in one atomic step. It marks the message burned, wipes the stored ciphertext and the hint, and hands the ciphertext to this one requester, all at once (a single conditional database update, shown below).
  4. The reader's browser decrypts in the tab, using the passphrase or the key in the link, and shows the message.

From step 3 on, the server holds no ciphertext for that message. The reader's tab does, and nothing else does.

Chat apps and mail gateways fetch links to build previews or check for malware. If a plain page load burned the message, the scanner would use it up before any person saw it. A burn message's ciphertext is never placed in the page. Only the open request, which the page's script sends when a person presses the button, releases it. So a scanner that just fetches the address gets the empty lock card and nothing is lost. To burn the message it would have to render the page, find the button and press it, and none of the common ones do. For a message you keep, a read is recorded only after the message has been successfully decrypted, using a token sealed inside it that a scanner can't get at. For a burn message the hand-over itself is what's counted, before anyone knows whether the passphrase was right. That's why the receipt says "Opened and burned" and not "read". Read receipts without tracking explains both.

Two readers at once

If two people press the button in the same instant, only one of them can get the message. The server guarantees that with a database update that succeeds only if the message is still live:

The shape of the burn: the "and state='live'" makes it first come, first served

update cipher_msg set state='burned', payload='', hint=NULL, ended_at=…
 where id='…' and state='live'

Both requests try it. The database lets exactly one of them change the row, and the server counts the rows changed. The request whose update changed one row receives the ciphertext. The other changed none, so it gets an error and the page says "Already gone." There's no moment when both could read it, because only the winning request gets the ciphertext back.

What "deleted" means here, exactly

When a message ends, whether by burning, by someone pressing Destroy, or by reaching its expiry time, its ciphertext and hint are overwritten with empty values in the database and its attachments are removed. The server keeps a tombstone: the id, the mode, whether it burned, when it was sealed and when it ended, how many times it was opened, and a SHA-256 of the receipt token. There's no content in it. The tombstone exists so the sender's receipt page can say "Opened and burned" instead of "not found", and it's deleted 90 days after the message ends.

From the application's point of view that's a real deletion, and it has three honest limits. The web host runs the database and keeps its own backups on a schedule we don't control, so a copy of the ciphertext may survive for a while in a backup. It's ciphertext, unreadable without a key the server never had. Access logs record that a request happened, with the address but not the key. And a MySQL row update doesn't scrub the disk blocks it used. None of this weakens secrecy where it matters, since what's left is encrypted, but we won't claim the bytes are physically destroyed.

Attachments: one fetch, fifteen minutes

Attachments are separate encrypted files, and a reader's page downloads and decrypts them right after unsealing. So for a burn message the server keeps each attachment for a short grace period after the burn: each file can be fetched once, within 15 minutes. A fetched file is deleted as it's served, and one nobody fetches is refused after the window and deleted by the next housekeeping run. Files are encrypted with the message's key, which the server never had, so an attachment waiting out its window is still unreadable to the server.

If the passphrase is wrong, after the burn

There's one awkward consequence. The server can't know whether a passphrase is right. Only the browser can, after decrypting. So the server releases and destroys a burn message before anyone learns whether the passphrase works. If the reader then mistypes it, the server's copy is already gone and the ciphertext lives only in that browser tab. The page says so and lets them keep trying, but if they close the tab it's lost for good. We accept that because the alternative, burning only after a correct passphrase, would require a server-side check of the passphrase. The server could then verify guesses, and that's the one thing it must never be able to do.

What no website can promise

Burn after reading controls the server's copy and the number of fresh readers. It can't control what a person does after reading. The recipient can take a screenshot, photograph the screen, copy the text, forward it or write it down. A reader whose computer is compromised exposes everything on it. A link shared in a group chat is opened by whoever in the chat is quickest, who may not be the person you meant, and that's a reason to use passphrase mode and say the passphrase out loud instead of relying on the link alone. And the sender can't open the message to check it without using it up, which is why the Preview button is hidden on the sealed card for a burn message.

So think of the feature as a limit on exposure over time. The message isn't sitting on a server waiting for the next person who gets the link, and it can't be opened years later from an old chat history. It's a good fit for passwords, codes and one-time instructions. It won't make a recipient forget something, and it's no guarantee about what the recipient does with it.

How cipher.ink uses this

You turn it on with the Burn after reading switch next to the expiry in the lock options. Pair it with a short expiry (the choices are 10 minutes, 1 hour, 1 day, 1 week (the default), 30 days, or never) and the message ends at whichever comes first, being read or running out of time. The sender can always check the state from the receipt link or My messages, and destroy a message early. The security page lists the other limits, and the burn code is short enough to read in the annotated source.

Further reading

  1. OWASP: Secure Coding Practices, data protection and session management
  2. MySQL: UPDATE Statement
  3. W3C: Referrer Policy (why a page can be fetched with no referrer)