- Shipped
- September 3, 2026 at 5:33 PM UTC
- Author
- Kamo
- Commit
- 77f7021
Bridges a browser to a PTY on the member's own dev machine. Binary frames are the terminal, text frames are control; on the far hop an HTTP 101 onto a length-prefixed stream reaches the agent on the port the network policy already permits, so no Cilium change is needed. The first WebSocket in this service, and deliberately a raw one. STOMP is a message broker over a socket and what travels here is a byte stream between a browser and a shell — an envelope around every keystroke buys nothing. AUTHORIZATION HAPPENS BEFORE THE SOCKET. A browser cannot put a header on a WebSocket constructor, so rather than teach the handshake to parse ***, an ordinary authenticated endpoint makes the whole decision with the session in hand and hands it forward as a single-use ticket that is worthless 30 seconds later and again after one use. That mirrors the KamoDesktop link, which was hardened for exactly this reason: a replayable token sits in history and in Ctrl+Shift+T, and a shell is worse in that respect than a desktop, not better. The handshake reads nothing else from the client — not a username, not an account, only a size hint the agent clamps anyway. The Linux account comes from credentialsFor(<the caller's own member id>), so a request cannot name somebody else's shell, and Origin is pinned to the host being addressed. No password is read at all: the agent is root and drops into the account, so the reversible credential Guacamole needs is never touched on this path. Audited before the fact, synchronously, into the same Access Log that already answers "who signed in from where" — with IP, geo and user-agent, at HIGH severity. A row that only appeared on success would be missing exactly the cases that matter. Eight terminals per member, because each is a PTY and a thread on a shared VM that has been taken down by memory pressure before. Closing the socket does not kill the shell; the agent runs it under tmux so a refresh or a dropped connection reattaches to the work still running. 21 tests: single-use, expiry either side of the boundary, unknown-token silence, per-member cap and its release, non-sequential tokens, and the frame codec including one-byte-at-a-time delivery, an absurd declared length, and reading the upgrade's status line without swallowing the stream behind it.