Skip to content

[REVIEW] api-security: add WebSocket and SSE session-boundary evidence #423

@DENGXUELIN

Description

@DENGXUELIN

Skill Being Reviewed

Skill name: api-security
Skill path: skills/appsec/api-security/

False Positive Analysis

Benign code that can be over-reported if every WebSocket is treated as high risk:

wss.on("connection", (socket, request) => {
  if (request.headers.origin !== "https://app.example.com") {
    socket.close(1008, "origin rejected");
    return;
  }
  socket.send(JSON.stringify({ type: "public_status", value: "ok" }));
});

Why this is a false positive:

A read-only public status stream with explicit origin handling and no cookie/session credentials is not the same risk as a cookie-authenticated account stream or privileged command channel. The current skill inventories REST, GraphQL, and gRPC, but it does not guide reviewers to classify browser-upgraded streaming APIs by data sensitivity, credential type, and command capability.

Coverage Gaps

Missed variant 1: cookie-authenticated WebSocket accepts any Origin

const wss = new WebSocketServer({ server, path: "/account/events" });

wss.on("connection", (socket, req) => {
  const session = parseCookieSession(req.headers.cookie);
  if (!session?.userId) socket.close();
  socket.on("message", msg => handleAccountCommand(session.userId, msg));
});

Why it should be caught:

Browsers include cookies in WebSocket handshakes, but the same-origin policy does not protect WebSocket connections the same way it protects normal XHR/fetch responses. A cookie-authenticated WebSocket that does not validate Origin can be exposed to cross-site WebSocket hijacking. The current API skill has CORS guidance and GraphQL guidance, but no WebSocket handshake origin, cookie-auth, or subprotocol evidence gates.

Missed variant 2: authorization is checked only at connection time for long-lived streams

socket.on("message", async raw => {
  const { tenantId, channel } = JSON.parse(raw);
  subscribe(socket, `tenant:${tenantId}:${channel}`);
});

Why it should be caught:

Long-lived WebSocket/SSE sessions can outlive role changes, account disablement, token revocation, or tenant membership updates. Reviewers should verify per-subscription authorization, reconnect behavior, token expiration handling, and forced disconnect on privilege changes. The current skill's BOLA/BFLA guidance is request/endpoint-oriented and can miss per-message or per-subscription authorization.

Missed variant 3: Server-Sent Events leak tenant data through weak query-token handling

app.get("/events", (req, res) => {
  const token = req.query.token;
  const claims = verifyJwt(token);
  streamTenantEvents(res, req.query.tenantId);
});

Why it should be caught:

SSE endpoints often use long-lived HTTP responses and sometimes pass tokens in query strings for browser compatibility. That creates logging, referrer, caching, and replay risks if reviewers do not treat streaming endpoints as API surfaces. The skill should add evidence checks for token placement, cache headers, replay windows, and tenant/channel authorization.

Edge Cases

  • Non-browser clients may omit Origin; the fix should not blindly require Origin for every machine-to-machine WebSocket. Instead, classify browser-exposed cookie-authenticated endpoints separately.
  • Some systems use bearer tokens in Sec-WebSocket-Protocol; reviewers should verify redaction and protocol parsing rather than treating it as ordinary CORS.
  • Public market/status streams are not automatically sensitive, but they still need resource limits and abuse controls.
  • GraphQL subscriptions inherit both GraphQL resolver authorization and WebSocket/SSE transport concerns.

Remediation Quality

  • Fix resolves the vulnerability
  • Fix doesn't introduce new security issues
  • Fix doesn't break functionality
  • Issues found: Add WebSocket/SSE inventory and evidence requirements: transport path, browser exposure, credential type, Origin policy, subprotocol/token handling, per-message/per-subscription authorization, token expiry/revocation behavior, tenant/channel scoping, rate limits, max connections, message size limits, and cache/logging controls for SSE.

Comparison to Other Tools

Tool Catches this? Notes
Semgrep Partial Can match WebSocket server constructors and SSE routes, but needs context for browser exposure, cookie auth, and per-message authorization.
CodeQL Partial Can analyze handler data flow, but long-lived session state and revocation behavior usually need manual review.
OWASP WebSocket Security Cheat Sheet Yes conceptually Covers Origin validation, authentication, authorization, input validation, and DoS controls that should be translated into skill evidence.
Existing api-security skill Partial Covers auth, rate limits, and CORS for request/response APIs, but not streaming session boundaries.

Overall Assessment

Strengths:

  • Good API inventory gate and OWASP API Top 10 mapping.
  • Strong BOLA/BFLA distinction for normal endpoints.
  • Useful authentication and rate-limit guidance.

Needs improvement:

  • WebSocket and SSE endpoints are not explicitly inventoried.
  • CORS guidance does not cover WebSocket Origin validation.
  • Long-lived session authorization, revocation, and tenant/channel subscription checks are missing.

Priority recommendations:

  1. Add discovery patterns for new WebSocketServer, ws, socket.io, EventSource, text/event-stream, SseEmitter, GraphQL subscriptions, and Sec-WebSocket-Protocol.
  2. Add a streaming endpoint table with credential type, Origin policy, auth refresh/revocation behavior, per-message auth, tenant/channel scope, connection limits, and message-size limits.
  3. Add severity guidance for cross-site WebSocket hijacking, tenant subscription BOLA, token-in-query SSE, and missing streaming resource limits.

Sources Checked

Bounty Info

  • I have read and agree to the CONTRIBUTING.md bounty terms
  • Preferred payment method: Crypto; details can be provided privately after acceptance

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions