From 9db70593c2a740755caa71cf7d0ca7def88dc7bd Mon Sep 17 00:00:00 2001 From: Ken Date: Thu, 28 May 2026 05:28:39 +0000 Subject: [PATCH] docs: resolve auth/handoff design - token-based auth, no Python relay --- ...26-05-28-terminal-architecture-redesign.md | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/superpowers/specs/2026-05-28-terminal-architecture-redesign.md b/docs/superpowers/specs/2026-05-28-terminal-architecture-redesign.md index e5ce496..d946ae8 100644 --- a/docs/superpowers/specs/2026-05-28-terminal-architecture-redesign.md +++ b/docs/superpowers/specs/2026-05-28-terminal-architecture-redesign.md @@ -55,7 +55,7 @@ Browser Server │ │ │ control protocol │ └──────────────────┘ └─────────────────────────────┘ ▲ -┌──────────────────┐ │ unix socket +┌──────────────────┐ │ process mgmt only │ app.js │ ┌────────┴────────────────────┐ │ (dashboard, │◄── HTTP ──►│ Python FastAPI │ │ sidebar, grid, │ │ dashboard API, settings, │ @@ -64,7 +64,7 @@ Browser Server └─────────────────────────────┘ ``` -Python starts muxterm as a managed subprocess on startup (one process, started once). muxterm listens on a Unix socket (e.g., `/tmp/muxterm.sock`) — not a TCP port. FastAPI handles the initial WebSocket upgrade at `/terminal/ws` (auth check, then hands off the raw connection to muxterm). After the handshake, Python is out of the data path entirely. +Python starts muxterm as a managed subprocess on startup (one process, started once). muxterm listens on a localhost TCP port (e.g., 7682 — recycling the old ttyd port). The browser connects directly to muxterm's WebSocket endpoint after obtaining a short-lived auth token from Python. Python is never in the terminal data path — not even for the initial WebSocket handshake. ## Components @@ -137,13 +137,14 @@ No explicit "screen dump" mechanism. Lean on tmux's native redraw behavior (same ### Auth flow -1. Browser opens `ws:///terminal/ws` -2. Python FastAPI runs `_ws_auth_check()` (cookie/bearer/localhost — same as today) -3. If authorized, upgrades HTTP connection to WebSocket -4. Hands the raw socket to muxterm via Unix socket passthrough -5. Python is out of the data path from this point +1. Browser requests `GET /api/terminal-token` from Python FastAPI +2. Python runs auth check (cookie/bearer/localhost — same as today) +3. If authorized, returns a short-lived HMAC token (30-second TTL, shared secret with muxterm) +4. Browser opens WebSocket directly to muxterm's port, including the token +5. muxterm validates the HMAC locally (no callback to Python) and accepts the connection +6. Python is never in the terminal data path — not even for the initial handshake -muxterm has zero auth logic. Trusts any connection on its internal Unix socket (not network-exposed). +muxterm validates tokens via HMAC with a shared secret configured at startup. No network calls to Python during connection. ## Wire Protocol @@ -204,15 +205,15 @@ Vendored into `muxplex/frontend/vendor/` alongside existing vendor pattern. WASM ### Process lifecycle - Python FastAPI starts muxterm as a managed subprocess on startup (one process, started once) -- muxterm listens on a Unix socket — not a TCP port +- muxterm listens on a localhost TCP port (e.g., 7682) - If muxterm dies, Python detects it and restarts it (simple process supervision) - On shutdown, Python sends SIGTERM to muxterm, which gracefully closes all PTYs - PTYs are just file descriptors — tmux sessions survive muxterm restarts (tmux is independent) ### What this replaces -- No `kill_orphan_ttyd()` — no orphans possible (Unix socket, single process) -- No port range allocation — Unix socket, not TCP +- No `kill_orphan_ttyd()` — single known process, Python holds the handle +- No port range allocation — single fixed port, not a dynamic pool - No PID file management — Python holds the process handle directly ## What Gets Deleted @@ -268,13 +269,14 @@ Either phase can ship and be tested independently. 3. **SIGWINCH for screen sync on session switch:** Instead of building a screen dump mechanism, leverage tmux's native redraw (triggered by SIGWINCH). This is exactly what happens when you resize a terminal — tmux repaints everything. -4. **Unix socket, not TCP:** Eliminates the port range allocation, port conflicts, and orphan scanning that caused so much complexity. Python connects to muxterm via a well-known socket path. +4. **Token-based auth, not socket relay:** Python issues short-lived HMAC tokens; the browser connects directly to muxterm. Eliminates any Python relay (even at the socket level). muxterm validates tokens locally with a shared secret — no callback to Python during connection. This is the cleanest "Python out of the data path" design. 5. **ghostty-web over xterm.js:** API-compatible drop-in with better VT100 parsing via WASM. The terminal industry is moving this direction (Coder's ghostty-web has 2.5K stars). Reduces parser bugs that are outside our control. ## Open Questions +- ~~**Socket handoff mechanism (RESOLVED):**~~ muxterm owns its own WebSocket listener on a localhost TCP port. Python is never in the terminal data path. Auth works via short-lived HMAC tokens: Python's `/api/terminal-token` endpoint authenticates the user (cookie/bearer/localhost) and returns a 30-second HMAC token. The browser includes this token when connecting to muxterm's WebSocket. muxterm validates the HMAC locally (shared secret with Python, no callback). This eliminates any form of Python relay or socket splicing. - **ghostty-web maturity:** How stable is the API surface? Are there xterm.js addon equivalents (search, web links, fit) or do those need custom implementation? - **WASM bundle caching:** Does the ~400KB ghostty-web WASM bundle cache well across page loads, or does it need explicit Cache-Control headers? -- **muxterm socket path:** Should the Unix socket path be configurable, or is a well-known path (e.g., `/tmp/muxterm-{user}.sock`) sufficient? +- **muxterm port:** Should the localhost port be configurable (env var / settings), or is a fixed default (e.g., 7682) sufficient? - **Federation implications:** Remote muxplex instances currently proxy through the Python WS relay. Does muxterm need its own federation-aware mode, or does Python remain the federation proxy (now forwarding to remote muxterm instances)?