61 lines
3.3 KiB
Markdown
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
|