Skip to content

SEC: WebSocket JWT passed in URL query param — CSWSH and token leakage #550

Description

@vybe

Summary

Trinity's WebSocket endpoint accepts a JWT via `?token=` URL query parameter. While this fixes the original unauthenticated access finding, the approach introduces Cross-Site WebSocket Hijacking (CSWSH) and leaks the JWT in browser history, proxy logs, and server access logs. This was flagged as Partially Fixed in the April 2026 UnderDefense remediation pentest (finding 3.2.1, residual CVSS ~2.1).

Context

The original finding required adding authentication to `/ws`. The fix added `?token=` in the URL. The remediation pentest flagged the remaining risk:

  1. CSWSH: A malicious page can initiate a WebSocket connection to Trinity using the victim's browser session (cookies/credentials sent automatically for same-origin).
  2. Token leakage: JWT appears in nginx access logs, browser history, and any intermediate proxy logs — a 7-day token exposed in logs is high-value.

The standard fix is a short-lived one-time WS ticket:

  1. Authenticated client calls `POST /api/ws/ticket` → backend returns a random opaque token (e.g., 32-byte urlsafe, 30s TTL in Redis)
  2. Client connects to `/ws?ticket=<opaque_token>`
  3. Backend exchanges ticket → user session; ticket is single-use and deleted on first use

This breaks CSWSH (ticket can't be forged cross-site) and eliminates JWT exposure in logs.

Acceptance Criteria

  • `POST /api/ws/ticket` endpoint added — requires JWT auth, returns short-lived opaque ticket (30s TTL)
  • `/ws` endpoint accepts `?ticket=` instead of `?token=` (ticket exchanged for session on connect)
  • Ticket is single-use (deleted from Redis on first successful exchange)
  • JWT no longer appears in nginx access logs when establishing a WebSocket connection
  • Frontend (`utils/websocket.js`) updated to fetch ticket before opening WS connection
  • Backward compatibility: graceful error if ticket is expired or already used

Technical Notes

  • Current WS auth: `src/backend/main.py` — `/ws` endpoint reads `?token=` query param
  • Frontend WS client: `src/frontend/src/utils/websocket.js`
  • Redis already available for ticket storage (same pattern as OAuth state)
  • Ticket TTL of 30s is sufficient — client fetches ticket immediately before connecting
  • Also applies to `/ws/events` (MCP events WebSocket) if it has the same pattern

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    complexity-mediumComplexity: medium (board points 5-8)pentestFrom penetration testing reportpriority-p2ImportantsecuritySecurity vulnerabilityseverity-mediumMedium severity security findingtheme-securityTheme: Security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions