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 like single_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.

RabbitChat architecture – E2EE between browsers, FastAPI as encrypted relay
Conceptual view – browsers handle E2EE, FastAPI just routes encrypted messages and events.

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:

  1. Enters a username, strong password, and a room name.
  2. The browser generates an ECDH keypair and a random 256-bit room key.
  3. The frontend calls /api/room/create with the public key, fingerprint and metadata.
  4. The backend hashes the password with Argon2, creates an in-memory room and returns: room_code, session_token, and flags like is_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:

  1. Enters username, password and the 24-digit room code.
  2. The browser generates its own ECDH keypair and fingerprint.
  3. The frontend sends a join-request to /api/room/join-request.
  4. The owner gets a join_request event over WebSocket with username + fingerprint.
  5. 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_accepted event 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 private in 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:

  1. The browser encrypts the file with the room key (AES-GCM) and base64-encodes it.
  2. It sends metadata + ciphertext to /api/file/upload with a single_use flag.
  3. The backend stores the encrypted payload and announces it via a file_announce event.
  4. When a user chooses “view” or “download”, the frontend calls /api/file/access.
  5. 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.