From 839a489883d9dfaf322bcf593b23708ddcb6210b Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 25 Mar 2026 20:53:44 +0100 Subject: [PATCH] fix(web-incoming): guard `req.socket` access in error handler use optional chaining for `req.socket?.destroyed` to prevent TypeError crash when `req.socket` is undefined (e.g. HTTP/2). ref: http-party/node-http-proxy#1634 --- src/middleware/web-incoming.ts | 2 +- test/middleware/web-incoming.test.ts | 40 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/middleware/web-incoming.ts b/src/middleware/web-incoming.ts index f02a1bc..bc8f8ea 100644 --- a/src/middleware/web-incoming.ts +++ b/src/middleware/web-incoming.ts @@ -134,7 +134,7 @@ export const stream = defineProxyMiddleware((req, res, options, server, head, ca function createErrorHandler(proxyReq: ClientRequest, url: URL | ProxyTargetDetailed) { return function proxyError(err: Error) { - if (req.socket.destroyed && (err as NodeJS.ErrnoException).code === "ECONNRESET") { + if (req.socket?.destroyed && (err as NodeJS.ErrnoException).code === "ECONNRESET") { server.emit("econnreset", err, req, res, url); return proxyReq.abort(); } diff --git a/test/middleware/web-incoming.test.ts b/test/middleware/web-incoming.test.ts index 2260ce3..5b61378 100644 --- a/test/middleware/web-incoming.test.ts +++ b/test/middleware/web-incoming.test.ts @@ -630,6 +630,46 @@ describe("#client abort propagation", () => { }); }); +// Regression: upstream http-party/node-http-proxy#1634 +// When req.socket is undefined, the error handler in stream middleware +// would throw TypeError: Cannot read properties of undefined (reading 'destroyed') +describe("#req.socket undefined", () => { + it("should not crash when req.socket is undefined and error occurs", async () => { + const { resolve, promise } = Promise.withResolvers(); + + // Use a port that refuses connections to trigger the error handler + const proxy = httpProxy.createProxyServer({ + target: "http://127.0.0.1:1", + }); + + const proxyServer = http.createServer((req, res) => { + // Set req.socket to undefined after proxyReq is emitted but before + // the error handler fires (simulates HTTP/2 or edge-case teardown) + proxy.once("proxyReq", () => { + Object.defineProperty(req, "socket", { + value: undefined, + writable: true, + configurable: true, + }); + }); + proxy.web(req, res); + }); + const proxyPort = await listenOn(proxyServer); + + proxy.once("error", () => { + // Should reach here without TypeError crash + proxyServer.close(); + resolve(); + }); + + const req = http.request(`http://127.0.0.1:${proxyPort}`, () => {}); + req.on("error", () => {}); // Ignore client-side errors + req.end(); + + await promise; + }); +}); + describe("#followRedirects", () => { it("should follow 301 redirect", async () => { const { resolve, promise } = Promise.withResolvers();