T09 · Insecure Skill Coding Practices
- Location
src/webhook.ts:170- Finding
Unauthenticated Media WebSocket Allows Unauthorized OpenAI Realtime Sessions
- Content
View full analysis
{ const url = new URL( request.url || "/", `http://${request.headers.host}`, ); if (url.pathname === streamPath) { console.log("[supercall] WebSocket upgrade for media stream"); this.mediaStreamHandler?.handleUpgrade(request, socket, head); } else { socket.destroy(); } }); ``` ```ts // src/media-stream.ts:61-72 handleUpgrade(request: IncomingMessage, socket: Duplex, head: Buffer): void { if (!this.wss) { this.wss = new WebSocketServer({ noServer: true }); this.wss.on("connection", (ws, req) => this.handleConnection(ws, req)); } this.wss.handleUpgrade(request, socket, head, (ws) => { this.wss?.emit("connection", ws, request); }); } ``` ```ts // src/media-stream.ts:149-178 private async handleStart( ws: WebSocket, message: TwilioMediaMessage, ): Promise { const streamSid = message.streamSid || ""; const callSid = message.start?.callSid || ""; // Guard against duplicate Twilio WebSocket connections for the same call. // Twilio sometimes sends two WS upgrades; the second would create a // competing OpenAI session and both end up dying. for (const existing of this.sessions.values()) { if (existing.callId === callSid) { console.log(`[MediaStream] Ignoring duplicate stream ${streamSid} for call ${callSid} (already have ${existing.streamSid})`); ws.close(); return null; } } console.log(`[MediaStream] Stream started: ${streamSid} (call: ${callSid})`); const instructions = this.config.getInstructionsForCall?.(callSid); const initialGreeting = this.config.getInitialGreetingForCall?.(callSid); const conversationSession = ...[truncated 2826 chars]- Remediation
View remediation
` URL or as a Twilio custom stream parameter. 3. Validate the token during or immediately after the WebSocket upgrade and bind it to the expected internal call ID and provider call SID. 4. Reject a `start` event unless: - The call SID maps to an active call. - The account SID matches the configured Twilio account. - The token is valid, unexpired, unused, and associated with that call. - The stream SID and media format are valid. 5. Do not create or connect an OpenAI session until all validation completes. 6. Add per-IP and global connection limits, handshake timeouts, maximum message sizes, and rate limits. 7. Close connections that send media before a valid `start` event or send duplicate/out-of-order events. 8. Consider using Twilio-supported request validation mechanisms where applicable, while retaining a call-bound nonce because ordinary WebSocket upgrade requests do not provide the same signed webhook body. ]]>
