From e74a1e0e3b498c515ac179c30ab2f7c272a0ce79 Mon Sep 17 00:00:00 2001 From: jh8459 Date: Tue, 29 Sep 2026 15:31:47 +0900 Subject: [PATCH] fix: bind ephemeral server to the loopback address it connects to --- lib/test.js | 34 +++++++++++++++++++++++++++++++--- test/issue-fixes.js | 12 ++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/lib/test.js b/lib/test.js index d4bfaf1..2de4a56 100644 --- a/lib/test.js +++ b/lib/test.js @@ -87,7 +87,10 @@ class Test extends Request { app.once('listening', onListening); app.once('error', onError); - app.listen(0); + // Bind to the same address the client connects to. A wildcard bind can + // share its port with another process's 127.0.0.1 listener on macOS, + // and the request would then reach that process instead. + app.listen(0, '127.0.0.1'); } addr = app.address(); } @@ -98,8 +101,29 @@ class Test extends Request { this._server = app; } - // A SuperTest-created IPv6 wildcard is dual-stack by default. Prefer IPv4 - // for that wildcard so SuperAgent does not construct an IPv6 loopback URL; + // Listening with a host resolves asynchronously, so the port is unknown + // until the server emits listening. end() fills it in before dispatch. + if (!addr) { + this._serverPath = path; + const protocol = app instanceof Server ? 'https' : 'http'; + return protocol + '://127.0.0.1' + path; + } + + return this.formatServerUrl(app, addr, path); + } + + /** + * Builds a request URL from a listening server address. + * + * @param {Server} app + * @param {Object} addr + * @param {String} path + * @returns {String} URL address + * @api private + */ + formatServerUrl(app, addr, path) { + // Map wildcard addresses of servers passed in already listening to the + // IPv4 loopback so SuperAgent does not construct an IPv6 loopback URL; // explicit IPv6-only servers retain their own address family below. let host = addr.address; if (host === '::') host = '127.0.0.1'; @@ -212,6 +236,10 @@ class Test extends Request { dispatch = () => { removeStartListeners(); try { + if (this._serverPath !== undefined) { + this.url = this.formatServerUrl(server, server.address(), this._serverPath); + this._serverPath = undefined; + } super.end(finish); } catch (err) { finish(err); diff --git a/test/issue-fixes.js b/test/issue-fixes.js index 71ef4f7..9d222cc 100644 --- a/test/issue-fixes.js +++ b/test/issue-fixes.js @@ -161,6 +161,18 @@ describe('GitHub Issue Fixes', function() { .expect(200, 'supertest-server'); }); }); + + it('should bind the ephemeral server to the address it connects to', function() { + // A wildcard bind can share its port with another process's 127.0.0.1 + // listener on macOS, and the client connection would reach that process. + const server = http.createServer(function(req, res) { + res.end(server.address().address); + }); + + return supertest(server) + .get('/') + .expect(200, '127.0.0.1'); + }); }); describe('Issue #891: request timeout startup timing', function() {