Files
Kordant/tasks/security-fixes/08-fix-websocket-jwt-query-param-leak.md
2026-05-29 09:03:47 -04:00

61 lines
3.3 KiB
Markdown

# 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 <jwt>`)
- 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