1. Why I Built RabbitChat
Most chat apps today fall into one of two buckets:
- Convenient but opaque: closed-source, E2EE “maybe” on, lots of telemetry.
- Enterprise-heavy: big stacks with databases, audit logs, and complex setup.
For small, high-trust groups you often just want:
- Strong, transparent end-to-end encryption.
- No database – everything gone when the room dies.
- Owner control over who gets in and who stays.
- One-time file sharing for sensitive screenshots and docs.
That’s the niche RabbitChat tries to hit: a privacy-first, in-memory, E2EE chat that you can run on your own box, read the code, and understand what happens to every piece of data.
Rooms are identified by 24-digit codes. You create a room with a username and password, share the code out-of-band, and approve join requests in real time. Messages and files are encrypted on the client, relayed as ciphertext, and kept only in memory while the room is alive.
2. Architecture & Crypto Stack
Under the hood, RabbitChat is split between a lightweight Python backend and a browser-based frontend that does the heavy cryptographic work.
2.1 High-Level Architecture
The backend is built with FastAPI + Uvicorn, exposing REST endpoints for room lifecycle and files, and a WebSocket endpoint for real-time chat:
POST /api/room/create– create a room, hash password, return room code + session token.POST /api/room/join-request– send a join request to the owner for approval.POST /api/file/upload– send encrypted file blobs with metadata and flags likesingle_use.POST /api/file/access– retrieve encrypted file payload once (view or download).ws://.../ws/{room_code}?session_token=...– WebSocket transport for encrypted chat + system events.
The server stores everything in in-memory structures: rooms, sessions, pending join requests, file metadata and ciphertext. When the last user leaves a room, the room and its files are removed from memory. No database, no disk writes – by design.
2.2 Cryptography Choices
Cryptography lives in the browser using the Web Crypto API. Each client:
- Generates an ECDH keypair on the P-256 curve.
- Derives shared secrets to unwrap the room key when the owner approves them.
- Encrypts messages and files with AES-GCM 256-bit.
The backend never sees plaintext; it only forwards ciphertext and handles things like join approvals, ownership transfers, private mode toggles, and file access checks. Passwords are hashed server-side with Argon2 before being stored in RAM.
3. Implementation & UX Walkthrough
Let’s look at RabbitChat from the perspective of an owner and a participant.
3.1 Creating a Secure Room
From the UI, the owner:
- Enters a username, strong password, and a room name.
- The browser generates an ECDH keypair and a random 256-bit room key.
- The frontend calls
/api/room/createwith the public key, fingerprint and metadata. - The backend hashes the password with Argon2, creates an in-memory room and returns:
room_code,session_token, and flags likeis_owner.
The owner then shares the 24-digit room code out-of-band (Signal, in person, etc.), never inside RabbitChat itself. This is a design choice: codes are treated as secrets.
3.2 Joining, Approval & Key Exchange
A participant joining the room:
- Enters username, password and the 24-digit room code.
- The browser generates its own ECDH keypair and fingerprint.
- The frontend sends a
join-requestto/api/room/join-request. - The owner gets a
join_requestevent over WebSocket with username + fingerprint. - The owner chooses accept or deny from the UI.
When the owner accepts:
- The owner’s browser uses ECDH with the participant’s public key.
- It encrypts the room key with the shared secret.
- RabbitChat sends a
join_acceptedevent containing the encrypted room key. - The participant decrypts the room key locally and can now read/send messages.
3.3 Private Mode & Ephemeral Messages
RabbitChat has a private mode switch that turns the room into “ephemeral chat”:
- Messages are marked as
privatein events. - The frontend renders them with special styling (e.g. badge / icon).
- They are automatically removed from the local UI after about 60 seconds.
The backend still only sees ciphertext; the “ephemeral” behavior is enforced in the clients and in the in-memory storage logic. Once the room empties or a message expires, it is removed from RAM.
3.4 One-Time File & Image Sharing
For sensitive screenshots, logs or photos, RabbitChat uses encrypted single-use file records:
- The browser encrypts the file with the room key (AES-GCM) and base64-encodes it.
- It sends metadata + ciphertext to
/api/file/uploadwith asingle_useflag. - The backend stores the encrypted payload and announces it via a
file_announceevent. - When a user chooses “view” or “download”, the frontend calls
/api/file/access. - If the file is
single_use, the backend deletes it from memory after the first successful access.
Files never touch disk. Combined with the room’s natural lifetime, this makes RabbitChat a good fit for “we need to share this once, then destroy it” use cases.
4. Design Lessons & Trade-Offs
- No persistence is a feature, not a bug. It makes RabbitChat bad as a long-term archive but great for high-sensitivity conversations.
- Custom crypto is dangerous. RabbitChat relies on well-known primitives (ECDH P-256, AES-GCM, Argon2) and pushes as much logic as possible to Web Crypto instead of re-implementing algorithms by hand.
- Owner-first UX. The room owner has explicit control: join requests, kicks, private mode, and ownership transfer all flow through their session.
- RAM-only storage simplifies some things and complicates others. You don’t worry about wiping disks, but you do need to think about memory growth and process restarts.
- Protocol clarity pays off. Having a clear JSON protocol for WebSocket
messages (types like
chat_message,join_request,file_announce) makes it easier to extend the client in the future.
5. Roadmap – Where RabbitChat Could Go Next
RabbitChat already does a lot for a single-box project, but there are plenty of ideas to explore:
- Multi-node support with sticky sessions or a minimal in-memory cluster.
- Mobile-first UI so the chat feels natural on phones without sacrificing clarity.
- Hardware-backed keys on devices that support WebAuthn / secure enclaves.
- Per-room policies (max members, allowed file types, geofenced IP ranges).
- Audit-friendly “mirror” mode where a separate, encrypted log is kept for compliance but only decryptable with a separate key.
For now, RabbitChat stays focused: short-lived, strongly encrypted rooms that you control. You can clone the repo, inspect every piece, and run it on your own infrastructure.
💬 Want to discuss this?
Reach out on LinkedIn or send me an email. I’m always happy to talk about privacy-by-design, E2EE, Web Crypto, and small security-focused side projects like this.