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.

Overview

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

Puzzle 1: The Guard (mTLS Certificate)

Skills: PKI fundamentals, CSR generation, mTLS

The Challenge

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.

Solution

  1. Authenticate with GitHub

    > authenticate
    

    Complete the OAuth flow to receive a Golden Token.

  2. Exchange token for certificate

    <go to concierge desk>
    > give token to concierge
    

    The Concierge asks for a Certificate Signing Request (CSR).

  3. 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.

  4. 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.

  5. 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
    
  6. 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.