# 08. Fix WebSocket JWT leakage via query parameter meta: id: security-fixes-08 feature: security-fixes priority: P1 depends_on: [] tags: [implementation, tests-required, medium-severity] objective: - Move WebSocket JWT authentication from query parameter to Authorization header to prevent token leakage in logs deliverables: - Updated `getTokenFromRequest()` at `web/src/server/websocket.ts:39-43` to read from Authorization header - Updated WebSocket client code to send JWT in the Authorization header - Backward compatibility during transition period (optional) - Unit tests for token extraction from headers steps: 1. Examine `web/src/server/websocket.ts:39-43` (`getTokenFromRequest`) and `web/src/server/websocket.ts:56-67` (authentication flow) 2. Update `getTokenFromRequest()` to: - First check the `Authorization` header for a Bearer token (`Authorization: Bearer `) - Fall back to `?token=` query parameter with a deprecation warning (optional, for backward compatibility) - Return `null` if neither is present 3. Update the WebSocket client (browser app and extension) to: - Include the JWT in a custom header: `new WebSocket(url, { headers: { Authorization: `Bearer ${token}` } })` - Note: The WebSocket constructor doesn't support custom headers in browsers; use the `verifyClient` callback on the server side to read headers from the upgrade request - Alternative: Use an HTTP handshake approach or pass the token in a message after connection (with a timeout) 4. If browser WebSocket API limitations prevent header-based auth, implement a post-connection authentication message with a timeout (e.g., client must send `{type: "auth", token: "..."}` within 5 seconds) 5. Update `web/src/server/websocket.ts:80-102` (connection handler) to enforce the new auth flow tests: - Unit: `getTokenFromRequest()` extracts token from Authorization header - Unit: `getTokenFromRequest()` rejects connections without a token - Unit: Query parameter fallback (if implemented) logs a deprecation warning - Integration: WebSocket connection with valid Bearer token is authenticated - Integration: WebSocket connection without a token is rejected acceptance_criteria: - JWT is no longer passed in the query parameter by default - Server accepts JWT from Authorization header (or post-connection auth message if browser limitation requires it) - Connections without authentication are rejected - No JWT tokens appear in server access logs or proxy logs - Backward compatibility is handled (if implemented) with a deprecation path validation: - `cd web && bun test` — all tests pass - Connect via WebSocket with a valid token in the expected location and verify authentication succeeds - Connect without a token and verify the connection is rejected - Check server logs to confirm JWT tokens are not visible in URLs notes: - Finding p8-008: The browser WebSocket API does not support custom headers in the constructor - Recommended approach: Post-connection authentication message with a server-side timeout 1. Client connects without token 2. Server allows the connection but marks it as unauthenticated 3. Client sends `{type: "auth", token: "..."}` within 5 seconds 4. Server validates the token and upgrades the connection 5. Unauthenticated connections are closed after the timeout - This approach avoids JWT in URLs and works with the browser WebSocket API