hack-house/cmd_chat/__init__.py
leetcrypt e7bacc93da fix(security): comprehensive security hardening — TLS, HMAC WS auth, rate limiting, IP leak prevention
CRITICAL fixes:
- Auto-generated self-signed TLS certs (HTTPS/WSS by default)
- Removed session_key from /srp/verify response (was sent in plaintext)
- Replaced with HMAC-SHA256 ws_token for WebSocket authentication

HIGH fixes:
- WebSocket auth now validates ws_token via hmac.compare_digest()
- /clear endpoint requires Bearer admin_token (printed at server start)
- Password no longer required as CLI arg — supports env var + getpass prompt
- Removed user_ip from Message model (no longer broadcast to clients)

MEDIUM fixes:
- Rate limiter on /srp/init and /srp/verify (10 req/min/IP)
- MessageStore capped at 1000 messages (prevents RAM DoS)
- access_log disabled (was leaking request metadata)

LOW fixes:
- Username sanitization against rich markup injection
- Dead code removed from helpers.py

All 79 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 20:30:40 -07:00

69 lines
2.2 KiB
Python

import argparse
import getpass
import os
from cmd_chat.server.server import run_server
from cmd_chat.client.client import Client
def resolve_password(args_password: str | None, prompt: str = "Room password: ") -> str:
if args_password:
return args_password
if env_pw := os.environ.get("CMD_CHAT_PASSWORD"):
return env_pw
return getpass.getpass(prompt)
def main():
parser = argparse.ArgumentParser(description="Command-line chat application")
subparsers = parser.add_subparsers(dest="command", required=True)
serve_p = subparsers.add_parser("serve", help="Run server")
serve_p.add_argument("ip_address")
serve_p.add_argument("port")
serve_p.add_argument("--password", "-p", default=None)
serve_p.add_argument("--cert", default=None, help="Path to TLS certificate")
serve_p.add_argument("--key", default=None, help="Path to TLS private key")
serve_p.add_argument("--no-tls", action="store_true", help="Disable TLS (insecure)")
connect_p = subparsers.add_parser("connect", help="Connect to server")
connect_p.add_argument("ip_address")
connect_p.add_argument("port")
connect_p.add_argument("username")
connect_p.add_argument("--password", "-p", default=None)
connect_p.add_argument(
"--insecure", "-k", action="store_true",
help="Skip TLS certificate verification (for self-signed certs)",
)
connect_p.add_argument(
"--no-tls", action="store_true",
help="Connect without TLS (insecure)",
)
args = parser.parse_args()
if args.command == "serve":
password = resolve_password(args.password)
run_server(
host=args.ip_address,
port=int(args.port),
password=password,
cert_path=args.cert,
key_path=args.key,
no_tls=args.no_tls,
)
elif args.command == "connect":
password = resolve_password(args.password)
Client(
server=args.ip_address,
port=int(args.port),
username=args.username,
password=password,
insecure=args.insecure,
no_tls=args.no_tls,
).run()
if __name__ == "__main__":
main()