How this actually works

What protects a conversation in BurnerRoom

A plain-English walkthrough of the encryption and infrastructure controls in the running code. Every claim below describes what the deployed code does today — not what is planned, and not what the underlying library is capable of. Where a protection is weaker than you might assume, it is written down in Known limitations rather than left out.

Reflects commit cb6befd · 2026-08-22
Encryption
AES-256-GCM
room key on every message, plus a per-message ratchet key on the direct link
Message bodies
Ciphertext only
message text never leaves your device unencrypted
Room lifetime
Your choice
timed up to 24h, or no expiry at all
Accounts
None
no sign-up, no password, no email
Abuse control
20 msg / sec
per connection, enforced server-side
Relay credentials
~1 hour lifetime
generated fresh per session, never a fixed secret
Encryption Verified

Messages are encrypted before they leave your device

Every message is encrypted in your browser and stays encrypted for its entire journey. The key is derived from the secret half of your room key — a value that is never sent to us — so the encryption happens entirely on your device, and ours only ever handles the result.

That first layer is a single AES-256-GCM key per room, held for as long as that room's key generation lasts, with a fresh random initialisation vector for every message. On its own it gives you no forward secrecy: someone who obtains a room key can read every message sent in that room under that key. Rotating the key generation — which happens when you revoke a guest — is what puts a hard boundary between an old key and everything after it.

When the two browsers manage to open a direct link to each other, a second layer goes on top of that one. The two devices run a PQXDH key agreement — X25519 plus ML-KEM-768, so a recorded handshake is not decryptable by a future quantum computer either — and then a Triple Ratchet that mints a brand-new AES-256-GCM key for every single message and throws the old one away. Nothing sent over that link touches our servers at all, and a key recovered later does not open the messages that came before it.

The catch is that the second layer only covers the direct link. When that link cannot be established the app falls back to relaying through us under the room key alone, and it is those messages that are stored and readable to anyone holding the room key. That is written up under Known limitations rather than glossed over here.

Primitives: HKDF-SHA256 derivation from the room secret, AES-256-GCM with a 96-bit random IV per message, non-extractable keys via the WebCrypto API; PQXDH (X25519 + ML-KEM-768, signed prekeys) and a Triple Ratchet on the direct link — chat/client/core/keys.js, chat/client/core/comm.js, webcrypto-ratchet
Transport Partial

Two paths: browser to browser, or through our relay

The app prefers a direct WebRTC data channel between the two browsers. When it is up, messages go straight across it under the ratchet, and our servers never see them — not even as ciphertext, and not as a stored blob.

That channel is deliberately forced through a TURN relay rather than joining the two browsers directly, so neither participant learns the other's IP address. The trade is that the relay provider carries the traffic; it only ever sees encrypted bytes.

When the direct channel is not up — before it finishes negotiating, when a network blocks it, or when the other person is offline — messages fall back to a WebSocket connection to our server, which stores the encrypted blob and forwards it. We hold ciphertext and never the text itself, but we do handle those messages, and we can see the room identifier, message sizes, and when they are sent.

One exception, and it favours you: a large attachment — roughly a megabyte or more — is never handed to the relay while the other person is offline. It stays on your device and is re-sent over the direct channel once you are both connected, so no copy of it ever reaches us. The trade is that it needs you both online at some point, and that is exactly why there is a size threshold rather than a blanket rule: smaller messages and attachments are still left with us and collected later, because holding one of those costs little and the delivery guarantee is worth more.

Path: WebRTC data channel with relay-forced ICE transport, falling back to a WebSocket relay through a Cloudflare Durable Object — chat/client/core/comm.js, chat/src/room-do.js
Room access Verified

Half of your room key never leaves your device

A room key is two independent halves. The first half is what the server checks you in with, and it only ever stores a one-way hash of it, compared in constant time so it can't leak a partial match through timing. The second half is what every encryption key in your conversation is derived from, and once your key is in your hands, that half is never transmitted to us again.

There used to be an exception here, and there no longer is. Your Owner Key is now generated in your own browser and shown to you before you are sent to the payment page. What travels to us is a one-way hash of the check-in half and nothing else. Paying does not create your key — it activates the one your browser already made. There is no longer a column in our database that could hold a complete key, which is a stronger statement than a promise not to fill one.

A Guest Key carries the room's shared secret inside it, which is how two people reach the same encryption key without that secret passing through us. A room has one guest at a time. Revoking that guest rotates the room's secret outright, so a key you have withdrawn cannot decrypt anything that comes after it, even in the hands of someone who kept a copy of the string.

Scheme: BR1 split key (128-bit auth half, 128-bit secret half, Crockford base32), HMAC-SHA256 server-side, HKDF-SHA256 client-side — chat/client/core/keys.js, web/src/index.js
Storage Verified

What we hold, and for how long

Only messages that took the relay path are stored at all — anything that crossed the direct channel never reached us. What is stored is the encrypted blob your browser produced, opaque to us either way. Stored messages expire automatically after 7 days, a room holds no more than 5,000 stored blobs at once (delivery receipts and other control frames count toward that alongside messages), and a server-side alarm removes them on that schedule whether or not anyone reopens the room. Revoking a guest deletes them immediately, as part of the same operation.

Alongside the ciphertext we hold operational data that is not encrypted to you: the room identifier, connection slots, push notification subscriptions, and a hash used to admit connections to the room. This is what lets the service route messages at all, and it is described in the privacy policy.

Limits: 7-day message TTL with a server-side backstop sweep, 5,000 stored blobs per room max — chat/src/room-do.js
Ephemerality Partial

Rooms end, and take the conversation with them

A room ends in one of three ways: you give it a lifetime and that deadline arrives, you destroy it, or you revoke your guest. Any of the three deletes the conversation from our servers. A lifetime can be set when you open a room and extended later in 5/15/30-minute or 1-hour steps, up to a 24-hour ceiling, and that deadline is a real backend alarm — not a timer that stops counting when a tab closes. When it fires, every connection is force-closed and the room's access hash, keys and stored messages are wiped in a single delete.

A lifetime is optional. If you choose no expiry, the room stays available until you destroy it or revoke your guest. Stored messages in a never-expiring room are still swept after 7 days by a separate backstop, so an abandoned room does not keep its contents indefinitely.

Two of the three also clear the other person's device. A room reaching its expiry, and a guest whose key you revoke, both end with that browser erasing its saved copy of the conversation — the moment it learns the room is over, which means the next time it is running and connected. Destroying a room is the one that does not: it purges our stored copy and wipes your own device, but the other participant's browser is never told. See Known limitations.

Mechanism: Durable Object alarm() → full delete on room expiry; server-side purge on revoke; prefix-scoped sweep of stored messages on the 7-day backstop — chat/src/room-do.js, web/src/index.js
Identity Verified

No accounts, no password database, nothing to breach

There's no sign-up, no username, no password, and we never ask for your email address. Access to a room is the key itself: you hold it, we hold a one-way hash of half of it. The only "identity" involved in a session is a random token your browser invents for itself, and it disappears along with everything else when the room ends. There's no user table anywhere to leak.

Payment is handled by Stripe, which keeps its own record of the transaction under its own terms. We keep a record of the payment itself — amount, currency, status and Stripe's reference — because we're required to, for five years, and nothing that identifies you personally.

Notifications Verified

Push notifications carry no message content

If you turn on notifications, what travels is a bare category label — "new message," "reaction," and so on — plus the room identifier. Never the message itself. The text you see in the notification is a fixed phrase your own device fills in locally.

Push notifications are delivered by your browser vendor's push service — Google, Apple or Mozilla, depending on the browser. The payload is encrypted to your device, so that service cannot read even the category label or the room identifier. It can see that a notification was sent to your subscription, and when.

Payload: {type, room}, encrypted to the subscriber — chat/src/room-do.js, chat/client/service-worker.js
Relay credentials Verified

Relay credentials expire in about an hour

The TURN credentials that carry the direct browser-to-browser channel are short-lived and generated fresh for each session — not a fixed secret shipped in the app. There's nothing long-lived to extract from the client even if someone tried. Requesting a set requires the same room authentication as joining the room, so they aren't handed out to anyone who asks.

These credentials exist for the text data channel. BurnerRoom does not have voice or video calling — there is no microphone or camera access anywhere in the app.

TTL: ~3,600 seconds, generated server-side per authenticated request — chat/src/index.js
What this doesn't cover yet

Known limitations

This page reflects the BurnerRoom codebase at commit cb6befd (2026-08-22) and is checked against source rather than summarised from marketing copy. Code changes after this date aren't reflected — treat it as a snapshot, not a standing guarantee.
Save your Owner Key

This is your Owner Key.

Generated on this device a moment ago. We never receive it, so we cannot show it to you again and cannot recover it. Save it before you continue.

Owner Key
 
Important
  1. This key is shown here and nowhere else
  2. Save it somewhere private before paying
  3. Anyone with this key can enter the room
  4. We cannot recover the key if you lose it