It runs on a server I control, and that server cannot read anything you draw on it. Here is exactly how that works — and where it stops working.
Your drawing is encrypted in your browser before it goes anywhere. The key never leaves your device. The server stores a blob of bytes it has no way to interpret, and the realtime relay forwards those bytes between collaborators without being able to read them either.
Sharing a board means sharing one link. That link contains both the board's address and its key — but only the address ever reaches the server.
A URL has a part after the # called the fragment. Browsers never send it in an HTTP request. It exists purely client-side.
So a share link looks like /draw#room=abc123,KEY. The server sees a request for /draw and nothing else. The room ID and the key live only in your browser's address bar, and the key is passed to the encryption code without ever crossing the network.
Scenes are encrypted with AES-GCM using a 256-bit key generated by your browser. Every single save uses a fresh random initialisation vector — reusing one would leak information about the contents, so uniqueness is enforced by a test rather than assumed.
AES-GCM is authenticated encryption: if anyone alters a stored byte, decryption fails loudly instead of returning corrupted shapes.
You can optionally protect a board with a passphrase when you create it. The link then carries only the wrapped key, so the link on its own is useless — opening it asks for the passphrase before anything can be decrypted.
The wrapping uses Argon2id, a deliberately slow, memory-hard function, tuned to take roughly half a second. It is slow on purpose: if a link ever leaks, the passphrase is the only thing standing between an attacker and the board, and they can attack it offline at their leisure. A fast function would make guessing cheap.
Realtime collaboration needs a server in the middle. Ours knows three things: which connections are in which room, how large a message is, and how fast messages arrive.
It never inspects a message body. It has no database credentials, no keys, and it writes nothing to a log — not room IDs, not IP addresses. If someone compromised it completely, they would learn that somebody was drawing something, and nothing more.
Everything above assumes the code running in your browser is honest. That is a large assumption: this page is built on a few hundred open-source packages, and nobody has read all of them.
So the browser is instructed to refuse network connections to anywhere except this server. Not just for the drawing code — for everything on the page. A compromised dependency that captured what you type would have nowhere to send it. That is a rule the browser enforces, not a promise anyone is making.
The honest gap: this stops background requests, not navigation. Code could still send you to another site with data in the address. The browser rule that covered that was never widely implemented.
Pasted and dropped images are encrypted exactly like everything else. The bytes are stored separately from the drawing itself and fetched when needed, rather than being re-sent every time anyone moves a shape. There is a 5MB ceiling per image, and anything larger is refused with a message rather than failing quietly.
One consequence worth stating: because images are stored as separate items rather than buried inside the drawing, the server can see how many images a board has and roughly how large each one is. It still cannot see what any of them contain.
Updates get lost — a dropped connection, two people saving at the same instant, a bug. So every twenty seconds each open board re-sends and re-saves itself in full, whether or not anything changed. Anything missed corrects itself within one cycle rather than leaving two people looking at different boards until someone reloads.
The cost is worth being straight about: a board left open writes to the server every twenty seconds even when nobody is drawing. So a regular pattern of writes tells the server that somebody has this board open, where silence used to mean nothing was happening. It still reveals nothing about what is on the board — but it is more than the server knew before.
Encryption stops the server reading your board. It does not stop the server handing you an older copy of it — authentication proves a snapshot is genuine, not that it is current.
So every snapshot carries a version number inside the encrypted payload, where it cannot be forged. Your browser remembers the highest version it has seen and refuses anything lower. A server that tries to roll your board back gets caught.
A privacy claim is only worth as much as its exceptions, so here they are, plainly.
Burn… asks once to be sure, then deletes the room and every snapshot of it, immediately. Two steps because a mis-click on a shared board should not destroy it. It also signals everyone currently on the board: their canvas clears, their local copy is dropped, and the key is stripped from their address bar. Every share link stops working. Anyone holding a link can do this — if it is a panic button, it has to work for whoever is panicking.
Being precise about what that means: the board becomes unrecoverable through any interface, and what was stored was ciphertext whose key never reached the server. Burning also vacuums the database so the deleted rows are reclaimed rather than sitting on disk until routine maintenance happens to run.
It is still not a forensic wipe. Vacuuming marks space reusable rather than overwriting it, and the original write stays in the database's write-ahead log until those segments recycle. Undecryptable bytes can linger for a while.
Boards are deleted after 30 days without activity, by a job that runs daily. Worth knowing that the twenty-second sync above counts as activity, so a board left open in a forgotten tab keeps itself alive indefinitely. Web server request logging is switched off for the whiteboard pages, the API, and the relay, so there is no record of who opened which board or when.
Data that no longer exists cannot be leaked, seized, or subpoenaed — retention is a privacy control, not housekeeping.
Mostly because it is a satisfying problem. But the honest reason to build it this way is that "trust me, I won't look" is not a security model. Designing so that I cannot look is the only version of that promise which survives me being compromised, careless, or compelled.