I had so much fun putting this project together, I hope y’all enjoyed it.
The project ran on a Digital Ocean droplet, with Caddy proxying to a Python flask app. Caddy was set up to use TLS mode “verify if given”, so it would allow you to connect without a client certificate, but you could provide a client cert if you had one signed by the trusted CA. So, the first puzzle was getting in the door of the factory, which required a GitHub OAuth token and a CSR. Then, once the user gets a certificate back, they have to install it for use by their browser. The certificate contains the user’s email address and GitHub username.
I thought the integration between Caddy and the Flask app was clever: Caddy would pass along the certificate subject information to the app, and the player’s GitHub username became their username in the MUD. And we could save their email in the puzzle database (a simple SQLite DB).
This project was vibe coded with Claude Code. I would never have had the time to write a holiday project of this scale without a coding agent. The code is a total mess, it’s 2,800 lines of Python in one giant file, no tests, and I barely know how it works. A CLAUDE.md brings a little bit of order to the chaos. I do know that the SQLite database tracks what I need it to track for the leaderboard.
The MUD featured 6 puzzles, each with unique per-player solutions generated from your GitHub username. This meant no answer sharing—for the most part, everyone had to solve their own personalized version.
| Puzzle | Location | Theme |
|---|---|---|
| Guard | fountain | mTLS Client Certificates |
| Fortune Cookie | fortune_room + Dragon's Realm | X.509 Certificate Extensions |
| Stroopwafel | control_room | Cryptographic Hash Functions |
| Gingerbread | gingerbread_station | VM/Assembly Tracing |
| Cables | maze_center | Graph Traversal |
| Cookie JWT | cookie_station | JWT alg:none Vulnerability |
Skills: PKI fundamentals, CSR generation, mTLS
The Guard at the fountain blocks entry to the bakery unless you present a valid client certificate. This teaches the fundamental concept of mutual TLS authentication.
Authenticate with GitHub
> authenticate
Complete the OAuth flow to receive a Golden Token.
Exchange token for certificate
<go to concierge desk>
> give token to concierge
The Concierge asks for a Certificate Signing Request (CSR).
Generate a CSR (outside the game)
step certificate create --csr [email protected] bakery.csr bakery.key
The CSR subject CN must match the user’s GitHub email address from their OAuth token.
Upload CSR and receive certificate
The Concierge issues your Bakery Certificate signed by the Candy Factory CA.
The certificate has your GitHub email as its subject, and your GitHub username as a URI SAN. The GitHub username becomes your MUD handle.
Add the certificate and key to your local browser keychain, and reconnect with mTLS. Note that on macOS/Safari, this requires creating a .p12 bundle to add to Keychain Access:
step certificate p12 carl.p12 bakery.crt bakery.key --legacy
Enter the bakery.
The MUD validates that the certificate was issued by the trusted Candy Factory CA and extracts your GitHub identity from the URI SAN. The guard lets you through because you have a credential.