← All posts
WebSocketvibe codingsecurityreal-timeAI code

How to Prevent WebSocket Hijacking in Vibe-Coded Apps

O

OverMCP Team

Quick answer

WebSocket hijacking occurs when an attacker intercepts or takes over a WebSocket connection due to missing authentication and authorization checks. In vibe-coded apps, this often happens because AI tools generate real-time features without proper origin validation, token verification, or connection lifecycle management. To prevent it, always authenticate WebSocket connections on upgrade, validate the Origin header, and use token-based handshake mechanisms.

What is WebSocket Hijacking?

WebSocket hijacking is a security vulnerability where an attacker can establish or hijack a WebSocket connection to your server, bypassing normal HTTP security controls. Since WebSocket connections are long-lived and often used for real-time data (chat, notifications, live updates), a hijacked connection can lead to data leaks, unauthorized actions, or full account takeover.

In vibe-coded apps—built rapidly with AI tools like Cursor, Bolt.new, or v0—developers often skip proper WebSocket security because AI models generate functional but insecure code. The AI focuses on making the feature work, not on securing the transport layer. This leaves your app open to attacks like:

  • Cross-Site WebSocket Hijacking: An attacker tricks a user's browser into connecting to your WebSocket endpoint from a malicious site, then reads or sends messages as that user.
  • Unauthorized Connection: Anyone who knows your WebSocket URL can connect and receive sensitive data if no authentication is enforced.
  • What to check first

    Before deploying a vibe-coded app with WebSockets, run through this checklist:

  • [ ] Origin header validation: Does your server check the Origin header on the WebSocket upgrade request?
  • [ ] Token-based authentication: Do you require a valid token (JWT or session ID) in the connection query string or during the handshake?
  • [ ] Authorization on each message: Do you verify the user's permission for every incoming WebSocket message?
  • [ ] Connection lifecycle management: Do you handle disconnects, timeouts, and reuse properly?
  • [ ] No hardcoded secrets: Are there any hardcoded API keys or tokens in the WebSocket client code?
  • [ ] Use wss://: Are you using secure WebSocket (wss://) in production?
  • [ ] Rate limiting: Do you limit the number of connections per user/IP?
  • You can use a free AI app security scanner to automatically check for these issues in your vibe-coded app.

    Step-by-step fix

    1. Authenticate the WebSocket upgrade

    Never accept a WebSocket connection without verifying the user's identity. The most common approach is to pass a token (e.g., JWT) as a query parameter during the upgrade request.

    Example (Node.js with `ws` library and JWT):

    const WebSocket = require('ws');
    const jwt = require('jsonwebtoken');
    
    const wss = new WebSocket.Server({ port: 8080 });
    
    wss.on('connection', (ws, req) => {
      // Extract token from query string
      const params = new URLSearchParams(req.url.split('?')[1]);
      const token = params.get('token');
    
      if (!token) {
        ws.close(4001, 'Authentication required');
        return;
      }
    
      try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        ws.user = decoded; // Attach user info to the connection
        console.log(`User ${decoded.id} connected`);
      } catch (err) {
        ws.close(4001, 'Invalid token');
        return;
      }
    
      // Handle messages
      ws.on('message', (message) => {
        // Always authorize actions based on ws.user
        handleMessage(ws, message);
      });
    });

    Client-side (vibe-coded with Cursor or Bolt.new):

    const token = localStorage.getItem('jwt');
    const ws = new WebSocket(`wss://yourdomain.com/ws?token=${token}`);

    2. Validate the Origin header

    Even with token authentication, an attacker might trick a user's browser into connecting. Check the Origin header to ensure it matches your domain.

    wss.on('connection', (ws, req) => {
      const origin = req.headers.origin;
      const allowedOrigins = ['https://yourdomain.com', 'https://app.yourdomain.com'];
    
      if (!allowedOrigins.includes(origin)) {
        ws.close(4002, 'Invalid origin');
        return;
      }
      // ... rest of connection logic
    });

    3. Authorize every message

    Don't assume the user who connected is the same user for every message. Check permissions for each action.

    function handleMessage(ws, message) {
      const data = JSON.parse(message);
      switch (data.type) {
        case 'send_message':
          // Verify ws.user can send to this chat room
          if (!canSend(ws.user.id, data.roomId)) {
            ws.send(JSON.stringify({ error: 'Forbidden' }));
            return;
          }
          // Process message
          break;
        // other cases
      }
    }

    4. Use secure WebSocket (wss://) in production

    Always use wss:// (WebSocket over TLS) in production to encrypt the communication. Most hosting platforms like Vercel support this natively. You can run a Vercel security scanner to verify your deployment.

    5. Implement connection limits and timeouts

    Prevent resource exhaustion by limiting connections per user and setting idle timeouts.

    const connectionsPerUser = new Map();
    
    wss.on('connection', (ws, req) => {
      // ... after authentication
      const userId = ws.user.id;
      if (connectionsPerUser.has(userId) && connectionsPerUser.get(userId) >= 5) {
        ws.close(4003, 'Too many connections');
        return;
      }
      connectionsPerUser.set(userId, (connectionsPerUser.get(userId) || 0) + 1);
    
      ws.on('close', () => {
        connectionsPerUser.set(userId, connectionsPerUser.get(userId) - 1);
      });
    
      // Idle timeout
      ws.isAlive = true;
      ws.on('pong', () => { ws.isAlive = true; });
    });
    
    // Ping every 30 seconds
    setInterval(() => {
      wss.clients.forEach((ws) => {
        if (!ws.isAlive) {
          ws.terminate();
          return;
        }
        ws.isAlive = false;
        ws.ping();
      });
    }, 30000);

    Common mistakes

  • No authentication on WebSocket upgrade: AI-generated code often skips token verification because the AI assumes you'll add it later. Result: anyone can connect.
  • Trusting the client's identity: Some vibe-coded apps send the user ID in the first message without verifying it. An attacker can spoof any user.
  • Ignoring the Origin header: Even if you have tokens, a CSRF-like attack can force a user's browser to connect to your WebSocket from a malicious site, leaking data.
  • Hardcoding WebSocket URLs with secrets: Vibe-coded frontends sometimes include the WebSocket URL with an API key or token directly in the code. This exposes your backend to anyone who inspects the source.
  • No rate limiting on connections: AI tools might generate code that accepts unlimited connections, making your app vulnerable to DoS attacks.
  • Using `ws://` in production: Unencrypted WebSocket traffic can be intercepted, leaking all data.
  • How OverMCP helps

    Instead of manually checking every connection, you can use continuous security monitoring to automatically scan your vibe-coded app for WebSocket vulnerabilities, leaked secrets, and misconfigurations. OverMCP integrates with your Vercel, GitHub, or direct deployment to catch issues before they reach production.

    FAQ

    How do I test if my WebSocket is secure?

    You can use browser developer tools to inspect the WebSocket handshake. Look for tokens in the query string, check if the Origin header is validated, and try connecting from a different origin using a tool like wscat. Also, run a security headers checker to ensure your server sends proper headers like Sec-WebSocket-Origin.

    Can I use cookies for WebSocket authentication?

    Yes, but it's less secure because cookies are automatically sent by the browser, making them vulnerable to CSWSH (Cross-Site WebSocket Hijacking). If you use cookies, ensure SameSite=Strict and validate the Origin header. A token in the query string is generally safer.

    What if my WebSocket server doesn't support headers?

    Some WebSocket libraries don't easily expose the upgrade request headers. If that's the case, send a token as the first message after connection, and close the connection if it's invalid. This is still better than no authentication, but it's vulnerable to a race condition where the server might send data before the auth message arrives. Always prefer header-based authentication.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free