fix: send terminal auth via first WebSocket message instead of URL query param#170
fix: send terminal auth via first WebSocket message instead of URL query param#170
Conversation
Browsers can't send custom Authorization headers on WebSocket upgrade requests, causing terminal connections from web/mobile UIs to fail auth.
mobile/src/lib/api.ts
Outdated
| if (currentToken) { | ||
| url.searchParams.set('token', currentToken); |
There was a problem hiding this comment.
Auth token passed in URL query param over unencrypted ws:// connection. Tokens are visible in logs/history and sent in plaintext without TLS, allowing interception and account takeover.
Suggested fix: Use wss:// (WebSocket over TLS) instead of ws:// for encrypted transport. baseUrl should use https:// to ensure wss:// protocol. Additionally, consider passing tokens in the first WebSocket message after upgrade instead of URL params to avoid logging.
| if (currentToken) { | |
| url.searchParams.set('token', currentToken); | |
| baseUrl = `https://${normalizeHost(host)}:${port}`; |
Identified by Warden via security-review · high, high confidence
src/client/api.ts
Outdated
| if (this.token) { | ||
| url.searchParams.set('token', this.token); | ||
| } |
There was a problem hiding this comment.
Auth tokens in URLs can leak via browser history, access logs, proxy logs, and Referer headers. While this addresses WebSocket header limitations, consider session-scoped tokens or rotating short-lived tokens for WebSocket upgrades instead of passing long-lived auth tokens in URLs.
Suggested fix: Consider implementing a token exchange: use the main auth token to request a short-lived WebSocket-specific token from an HTTP endpoint, then pass that ephemeral token in the URL. This limits exposure if URLs are logged or leaked.
| if (this.token) { | |
| url.searchParams.set('token', this.token); | |
| } | |
| // TODO: Replace with short-lived WebSocket token from token exchange endpoint | |
| // to minimize exposure if URL is logged or leaked |
Identified by Warden via security-review · high, high confidence
src/client/ws-shell.ts
Outdated
| const url = new URL(`${wsProtocol}${host}/rpc/terminal/${encodeURIComponent(workspaceName)}`); | ||
| if (token) { | ||
| url.searchParams.set('token', token); | ||
| } |
There was a problem hiding this comment.
🟠 [94W-JJJ] Authentication token exposed in WebSocket URL query parameters (high confidence)
Authentication tokens passed via URL query parameters (?token=...) are exposed in browser history, server logs, and referrer headers. While necessary for WebSocket auth (browsers can't set custom headers on upgrade), this creates token leakage risk.
Suggested fix: The implementation is correct given WebSocket limitations. To mitigate risks: 1) Use short-lived tokens for terminal connections, 2) Ensure HTTPS/WSS in production to prevent network sniffing, 3) Consider token rotation after each terminal session. Document this security consideration.
| const url = new URL(`${wsProtocol}${host}/rpc/terminal/${encodeURIComponent(workspaceName)}`); | |
| if (token) { | |
| url.searchParams.set('token', token); | |
| } | |
| /** | |
| * Security note: Token is passed as query parameter due to WebSocket upgrade limitations. | |
| * Ensure WSS (not WS) is used in production to prevent token interception. | |
| */ |
Identified by Warden via security-review · medium, high confidence
…URL query param
Tokens in URLs leak via server logs, proxy logs, browser history, and
Referer headers. Move to first-message auth: the server upgrades the
WebSocket unconditionally, then expects { type: 'auth', token } as the
first message before allowing any terminal interaction. Connections that
fail auth are closed with code 4001.
Bearer header and Tailscale auth still work at the HTTP level for
upgrade requests that already carry credentials.
| @@ -169,6 +168,11 @@ function createAgentServer( | |||
| return new Response('WebSocket upgrade failed', { status: 400 }); | |||
There was a problem hiding this comment.
The terminal WebSocket endpoint allows upgrade before authentication. While deferred auth happens in the handler, this creates a window where unauthenticated WebSocket connections exist. The connection can be upgraded (line 161) even when authResult.ok is false, allowing unauthenticated clients to establish WebSocket connections that are only closed later in the handler.
Suggested fix: Reject WebSocket upgrade if authentication fails, or ensure auth check happens before upgrade
| if (!authResult.ok) { | |
| return unauthorizedResponse(); | |
| } | |
| data: { type, workspaceName, authenticated: true }, |
Identified by Warden via security-review · high, high confidence
| if (token) { | ||
| ws.send(JSON.stringify({ type: 'auth', token: token })); | ||
| } |
There was a problem hiding this comment.
🟠 [UZX-4PN] Authentication token sent over unencrypted WebSocket connection (high confidence)
The auth token is sent via WebSocket message without enforcing TLS/WSS. If users connect via ws:// instead of wss://, the token is transmitted in cleartext and can be intercepted by network attackers.
Suggested fix: While this file cannot directly enforce WSS, document that this auth method requires WSS in production. The enforcement should happen at connection time or in server configuration.
| if (token) { | |
| ws.send(JSON.stringify({ type: 'auth', token: token })); | |
| } | |
| // SECURITY: Token should only be sent over WSS (encrypted) in production |
Identified by Warden via security-review · medium, high confidence
| @@ -169,6 +168,11 @@ function createAgentServer( | |||
| return new Response('WebSocket upgrade failed', { status: 400 }); | |||
There was a problem hiding this comment.
🟠 [K44-7DE] Authentication mismatch with PR description (high confidence)
PR description states tokens are passed via query param (?token=), but the actual implementation uses WebSocket message-based authentication (auth.ts shows only Authorization header support, terminal handler expects auth messages). This inconsistency suggests either incomplete implementation or misleading documentation. If query params were intended but not implemented, tokens could be logged in server access logs, proxy logs, and browser history.
Identified by Warden via security-review · medium, high confidence
There was a problem hiding this comment.
🟠 [9MH-LFT] WebSocket authentication lacks rate limiting (high confidence)
Authentication attempts via WebSocket message have no rate limiting or account lockout. Attackers can repeatedly attempt authentication without throttling, enabling brute force attacks against the auth token.
Identified by Warden via security-review
Summary
Authorizationheaders on WebSocket upgrade requests, breaking terminal auth from web/mobile UIs{ type: 'auth', token }as first message before allowing interaction