Yahat Chat
Why did I need a chatserver?
I often install new VMs and reinstall laptops and PCs. It's part of who I am - spending time experimenting with new and old technologies, setting up CI pipelines locally, and reproducing problems with my various projects on specific OS versions.
Very often, I need passwords or configuration snippets from other machines. I'm pretty conservative when it comes to passwords, so I don’t trust the "Cloud" with them. I use a password manager that stores passwords locally in an encrypted file on my primary machine.
To share such information locally, a secure chat server is ideal. And when I say secure, I don't mean a chat server with layers of OAuth or similar nonsense - I don’t trust that at all. What I mean is that the communication between the web browser and the chat server is secure (i.e., not redirected to someone else) and that neither the browser nor the server leaks information to third parties.
A chat server that uses, for example, Node.js will never be truly safe - at least not if it relies on third-party dependencies or libraries pulled from the internet or a CDN. The same goes if the browser is made by Microsoft or Google or if the web app includes third-party JavaScript libraries. If a web app pulls JavaScript libraries from the internet when it loads, it becomes incredibly insecure. The recent polyfill.io supply chain attack illustrates this perfectly.
Another potential problem is telemetry and logging. If I install a server from someone on the internet, the server may work well - or it could secretly send information about my conversations with myself to its developers. It might log data or even store it in a local (or worse, cloud) database just to be helpful. Even if it’s written by someone who genuinely cares about security and privacy, it could still include a third-party library with hidden telemetry. This happens all the time with Android apps.
Building a simple and secure chat server
A chat server is actually a pretty simple thing. So when I was making improvements to my embedded C++ web server, Yahat, I decided to add a single-room chat server as an example project.
One of Yahat’s features is Server-Sent Events (SSE), which is an alternative to WebSockets - a "technology" I’m pretty sure was invented by the Devil himself to torture developers while we’re still alive. SSE is much better. The server uses SSE to notify users’ web apps about events in real-time - no polling, no delays. The chat updates instantly when a user joins or leaves, and when a message is received.
The chat server doesn’t store any information. It keeps a list of active user sessions in memory. When a message is received via an HTTP POST request, it’s sent to all users in the chat via their SSE channels, and then it’s forgotten. This means users will only see messages received after they join the chat.
There’s also a limit in the chat app - it only displays the last 100 received messages. When the app is closed (i.e., the browser tab is closed and its DOM is deleted), all chat information is forgotten. (Unless, of course, the browser is made by Microsoft or Google - in which case, any expectation of privacy is void and null - or if you have some creepy tracking extension installed.)
The web app does use JavaScript, but it’s minimal, easy to read, and embedded in index.html
. It doesn’t rely on external JS libraries, making it safe from JavaScript supply chain attacks.
The image above shows the chat running in Firefox on my Nokia Android phone. I haven't made the web app adaptive to different screen sizes, but it works fine in landscape mode.
Running Yahat Chat
I build a Docker image of the chat server as part of my GitHub Actions workflow, which runs automatically whenever I push updates to the Yahat source code. The repository is public, so anyone who can run a Docker image can try it out.
Here’s the command I use to keep it running (persistently across restarts) on one of my local servers:
1docker run --restart=always -d --name yahatchat -p 0.0.0.0:8008:8080 ghcr.io/jgaa/yahatchat
If you just want to try it out locally:
1docker run --rm -d --name yahatchat -p 127.0.0.1:8008:8080 ghcr.io/jgaa/yahatchat
Both examples will start the chat-server at port 8008. To connect to the local one, you can type: http://127.0.0.1:8008
into your web browser.
Note that this configuration uses HTTP without TLS, so data is sent unencrypted. Since my server runs on the local network, installing and updating Let's Encrypt TLS certificates for it isn’t entirely straightforward. However, it is possible. I’ll cover how to do that in a future blog post.
Yahat is freely available as Open Source under the MIT License on on Github