{"record":{"id":"5f32e409841a89b4","repo":"denoland/deno","slug":"socket-is-not-a-tcp-socket-only-tcp-connections","errorCode":null,"errorMessage":"Socket is not a TCP socket - only TCP connections can be upgraded to WebSocket","messagePattern":"Socket is not a TCP socket - only TCP connections can be upgraded to WebSocket","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/http/02_websocket.ts","lineNumber":165,"sourceCode":"    // sync with the header-list built above (protocol negotiation, etc.).\n    let responseHead = \"HTTP/1.1 101 Switching Protocols\\r\\n\";\n    for (let i = 0; i < r.headerList.length; i++) {\n      const { 0: name, 1: value } = r.headerList[i];\n      responseHead += `${name}: ${value}\\r\\n`;\n    }\n    responseHead += \"\\r\\n\";\n\n    if (nodeSocket.destroyed) {\n      throw new TypeError(\n        \"Socket is already destroyed - cannot upgrade to WebSocket\",\n      );\n    }\n    const handle = nodeSocket._handle;\n    if (!handle) {\n      throw new TypeError(\"Socket has no handle - cannot upgrade\");\n    }\n    if (typeof handle.takeStream !== \"function\") {\n      throw new TypeError(\n        \"Socket is not a TCP socket - only TCP connections can be upgraded to WebSocket\",\n      );\n    }\n\n    // Extra bytes that were already buffered (e.g., from the upgrade\n    // request body that arrived with the headers)\n    const extraBytes = options.head || new Uint8Array(0);\n\n    // Defer setup so the caller can attach event handlers (onopen,\n    // onmessage, etc.) before events fire.\n    (async () => {\n      try {\n        // Wait for the 101 response to fully flush before taking the\n        // stream. A fire-and-forget write could leave data in the\n        // internal_write_queue that would be orphaned once we detach\n        // the stream from libuv.\n        await new Promise((resolve, reject) => {\n          nodeSocket.write(responseHead, (err) => {","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/denoland/deno/blob/89f33cbef296a2b287f323d42de54c871fa69c77/ext/http/02_websocket.ts#L147-L183","documentation":"Third node-socket guard: the libuv handle must expose a takeStream function, which only TCP stream handles in Deno's node compat implement. Passing a socket whose underlying handle is not a TCP stream (e.g. a Unix domain socket, pipe, or TLS-wrapped handle that lacks takeStream) cannot be upgraded to a WebSocket this way.","triggerScenarios":"Calling the upgrade path with a Unix socket or named-pipe connection (server listening on a path); sockets from abstractions whose _handle is a custom object without takeStream; passing a TLS net.Socket whose handle type differs; interop with non-node runtimes that emulate the socket shape only partially.","commonSituations":"Servers listening on unix domain sockets for sidecar/IPC being reused for WS; test doubles mocking _handle as {}; adapters bridging other socket types into node-compatible shapes; internal-only listeners accidentally exposed to upgrade requests.","solutions":["Serve WebSocket upgrades only on TCP listeners (server.listen({ port }) / listen({ host, port })), not unix sockets/pipes.","Guard with typeof socket?._handle?.takeStream === 'function' before upgrading.","For unix-socket IPC, use a different transport (Deno.UnixConn / WebSocket over TCP)."],"exampleFix":"// before\nconst server = createServer();\nserver.listen(\"/tmp/app.sock\"); // unix socket\nserver.on(\"upgrade\", (req, socket, head) => {\n  Deno.upgradeWebSocket(req, { socket, head }); // handle has no takeStream\n});\n\n// after\nconst server = createServer();\nserver.listen(8080); // TCP\nserver.on(\"upgrade\", (req, socket, head) => {\n  if (typeof socket._handle?.takeStream !== \"function\") return;\n  const { socket: ws } = Deno.upgradeWebSocket(req, { socket, head });\n});","handlingStrategy":"type-guard","validationCode":"server.on(\"upgrade\", (req, socket, head) => {\n  if (typeof (socket as any)?._handle?.takeStream !== \"function\") return socket.destroy();\n  Deno.upgradeWebSocket(req, { socket, head });\n});","typeGuard":"function isUpgradableTcpSocket(s: unknown): boolean { const h = (s as { _handle?: { takeStream?: unknown } } | null)?._handle; return typeof h?.takeStream === \"function\"; }","tryCatchPattern":"try { Deno.upgradeWebSocket(req, { socket, head }); } catch (e) { if (e instanceof TypeError && e.message.includes(\"not a TCP socket\")) { socket.destroy(); return; } throw e; }","preventionTips":["Expose WebSocket upgrades only on TCP listeners, not unix sockets/pipes.","Keep a capability check on takeStream when bridging foreign socket types."],"tags":["websocket","node-compat","unix-socket","tcp"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}