Skip to content

Telnet Security

The telnet protocol predates TLS by decades and has no native encryption. When a player uses the connect <username> <password> command over a raw telnet socket, the credentials travel across the network in cleartext. Anyone on the path between the player and your server — shared Wi-Fi, ISP infrastructure, a hotel captive portal, a compromised upstream router — can capture the password with a packet sniffer.

This is not a bug in HoloMUSH. It is a property of the telnet protocol itself. This guide explains the risk, what HoloMUSH does and does not do about it, and which mitigations you should apply in production.

AspectDetail
What’s exposedUsername and password, plus every command typed after
Who can see itAny attacker with network visibility to the TCP stream
WhenEvery connect <user> <pass> on the telnet listener
Why it existsTelnet is a cleartext protocol with no TLS negotiation
What HoloMUSH doesRate limits, Argon2id hashing, identical error messages
What HoloMUSH cannot doEncrypt the wire without breaking telnet compatibility

The server’s credential handling is solid — see Authentication for storage and rate limiting details — but no amount of server-side hardening prevents an eavesdropper from reading the password off the wire before it reaches the server.

Apply one or more of these in any deployment that exposes telnet to the public internet.

The web client (port 8080, fronted by HTTPS in the standard Docker deployment) submits credentials over TLS. The safest path for players who care about credential confidentiality is to log in through the web client and, if they prefer a traditional MU* client, use telnet only after authenticating through the web.

Link players to the web client from your server’s announcement or wiki, and make it clear that telnet logins expose their password to the network path.

2. Front the Telnet Port with a TLS-Terminating Proxy

Section titled “2. Front the Telnet Port with a TLS-Terminating Proxy”

Put a TLS wrapper in front of the telnet listener so players connect with a TLS-aware MU* client (most modern clients — Mudlet, MUSHclient, TinTin++, BeipMU — support TLS). HoloMUSH speaks plain telnet on 127.0.0.1:4201; the proxy terminates TLS on a public port and forwards to the loopback.

stunnel example (/etc/stunnel/holomush.conf):

[holomush-telnet-tls]
accept = 0.0.0.0:4202
connect = 127.0.0.1:4201
cert = /etc/letsencrypt/live/mush.example.com/fullchain.pem
key = /etc/letsencrypt/live/mush.example.com/privkey.pem

Then restrict the plaintext port to loopback only:

Terminal window
# Only bind telnet to localhost so nothing reaches it except stunnel
TELNET_LISTEN=127.0.0.1:4201
# UFW: drop external 4201, allow 4202 (TLS)
ufw deny 4201
ufw allow 4202

Players connect their client to mush.example.com:4202 with TLS enabled.

haproxy example (/etc/haproxy/haproxy.cfg):

frontend telnet_tls
bind *:4202 ssl crt /etc/haproxy/certs/mush.example.com.pem
mode tcp
default_backend telnet_backend
backend telnet_backend
mode tcp
server core 127.0.0.1:4201

3. Document SSH Tunneling for Advanced Players

Section titled “3. Document SSH Tunneling for Advanced Players”

Players who cannot switch clients and cannot use TLS can tunnel telnet over SSH from any host they trust:

Terminal window
ssh -L 4201:localhost:4201 mush.example.com
# Then in their client:
telnet localhost 4201

This moves the cleartext traffic inside an SSH session, so the password is only cleartext on the loopback interface of the server. It requires an SSH account on the host, so this is realistic for admins and testers, not general players.

If all of your players use the web client, the cheapest mitigation is to not run the telnet listener at all. In the Docker deployment, comment out the telnet port mapping in compose.yaml or set TELNET_ENABLED=false in .env.

ActionWhy
Monitor telnet trafficUnexpected spikes can indicate credential-stuffing against the cleartext port
Log remote addressesFailed connect attempts are logged with remote_addr; review them for sweeping attack patterns
Review rate-limit logsThe per-username lockout (see Authentication) still applies on telnet
Rotate exposed credentialsIf a player reports they logged in from an untrusted network, have them reset their password
Announce the riskPut a warning in your MOTD or server description so players can make an informed choice

Telnet’s lack of encryption is a protocol-level limitation, not a HoloMUSH vulnerability. The server does what it can: strong hashing, rate limiting, account lockout, scrubbed error messages. The rest is an operator decision. The recommended production posture is: web client for login, TLS-terminating proxy for players who insist on a telnet client, and a clearly documented warning for anyone who still wants a raw telnet session.

The gateway enforces four operator-tunable limits on the telnet surface to prevent Slowloris, goroutine flooding, and unbounded pre-auth idle.

FlagDefaultWhat it bounds
--telnet-max-conns1000Concurrent telnet connections; new accepts beyond this receive a refusal line and close
--telnet-idle-timeout5mTime since the last byte read; an idle or drip-fed connection is closed
--telnet-write-timeout30sPer-send write deadline; a stuck client’s full send buffer cannot hold the handler
--telnet-pre-auth-timeout2mTime from connect to successful character selection; unauthenticated clients are disconnected

Size --telnet-max-conns to peak concurrent players × 1.5. Monitor holomush_telnet_connections_active and holomush_telnet_connections_refused_total via Prometheus; non-zero refusals under legitimate load mean the cap is too low.

The timeouts are chosen for a typical MUSH; very slow-typing players at the character picker may trip --telnet-pre-auth-timeout on large character inventories — raise to 5m if that affects legitimate users.

Four Prometheus metrics expose DoS state for operators:

MetricPurpose
holomush_telnet_connections_activeCurrent open connection count; primary DoS signal when it pins near the cap
holomush_telnet_connections_refused_totalCapacity refusals; sustained growth indicates attack or legitimate overload
holomush_telnet_idle_timeouts_totalRead-deadline disconnects; sustained growth suggests Slowloris
holomush_telnet_preauth_timeouts_totalUnauthenticated clients disconnected; expected non-zero from scanners