From 54742556b5b518fa4ededee72181065cab347ae9 Mon Sep 17 00:00:00 2001 From: abhijeet117 Date: Sun, 23 Aug 2026 23:52:19 +0530 Subject: [PATCH] fix(socks5-proxy-agent): destroy socket when negotiation times out --- lib/dispatcher/socks5-proxy-agent.js | 2 ++ test/socks5-proxy-agent.js | 39 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/lib/dispatcher/socks5-proxy-agent.js b/lib/dispatcher/socks5-proxy-agent.js index 203beca4cae..5a952d8cc6f 100644 --- a/lib/dispatcher/socks5-proxy-agent.js +++ b/lib/dispatcher/socks5-proxy-agent.js @@ -115,6 +115,7 @@ class Socks5ProxyAgent extends DispatcherBase { const authenticationReady = Promise.withResolvers() const authenticationTimeout = setTimeout(() => { + socks5Client.destroy() authenticationReady.reject(new Error('SOCKS5 authentication timeout')) }, 5000) @@ -148,6 +149,7 @@ class Socks5ProxyAgent extends DispatcherBase { const connectionReady = Promise.withResolvers() const connectionTimeout = setTimeout(() => { + socks5Client.destroy() connectionReady.reject(new Error('SOCKS5 connection timeout')) }, 5000) diff --git a/test/socks5-proxy-agent.js b/test/socks5-proxy-agent.js index 691497aeb94..ee05908da6c 100644 --- a/test/socks5-proxy-agent.js +++ b/test/socks5-proxy-agent.js @@ -428,6 +428,45 @@ test('Socks5ProxyAgent - connection failure', async (t) => { await p.completed }) +test('Socks5ProxyAgent - destroys socket when negotiation times out', async (t) => { + const p = tspl(t, { plan: 2 }) + + // SOCKS5 proxy that accepts the TCP connection but never replies to the greeting + const stalledProxy = net.createServer(() => {}) + await new Promise((resolve) => { + stalledProxy.listen(0, '127.0.0.1', resolve) + }) + + let connectorSocket + const agent = new Socks5ProxyAgent(`socks5://127.0.0.1:${stalledProxy.address().port}`, { + connect (opts, callback) { + const socket = net.connect({ host: opts.hostname, port: opts.port }) + connectorSocket = socket + socket.once('connect', () => callback(null, socket)) + socket.once('error', callback) + return socket + } + }) + + try { + await request('http://example.invalid/', { + dispatcher: agent + }) + p.fail('should have thrown an error') + } catch (err) { + p.ok(err, 'should throw error when SOCKS5 negotiation stalls') + } + + await agent.close() + p.ok(connectorSocket.destroyed, 'socket left by the timed out negotiation should be destroyed') + + t.after(async () => { + stalledProxy.close() + }) + + await p.completed +}) + test('Socks5ProxyAgent - proxy connection refused', async (t) => { const p = tspl(t, { plan: 1 })