From 4a4ad9b6c08b70117c70754fc4022bf5aecb1cb7 Mon Sep 17 00:00:00 2001 From: Khafra Date: Fri, 23 Jan 2026 00:39:23 -0500 Subject: [PATCH 1/2] fix fetch 401 loop Co-Authored-By: killagu Co-Authored-By: Claude Opus 4.5 --- lib/web/fetch/index.js | 5 ++++ test/fetch/401-statuscode-no-infinite-loop.js | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 test/fetch/401-statuscode-no-infinite-loop.js diff --git a/lib/web/fetch/index.js b/lib/web/fetch/index.js index 56e540d9d88..24d724c2218 100644 --- a/lib/web/fetch/index.js +++ b/lib/web/fetch/index.js @@ -1675,6 +1675,11 @@ async function httpNetworkOrCacheFetch ( // 4. Set the password given request’s current URL and password. // requestCurrentURL(request).password = TODO + + // In browsers, the user will be prompted to enter a username/password before the request + // is re-sent. To prevent an infinite 401 loop, return a network error for now. + // https://github.com/nodejs/undici/pull/4756 + return makeNetworkError() } // 4. Set response to the result of running HTTP-network-or-cache fetch given diff --git a/test/fetch/401-statuscode-no-infinite-loop.js b/test/fetch/401-statuscode-no-infinite-loop.js new file mode 100644 index 00000000000..4af1d08368f --- /dev/null +++ b/test/fetch/401-statuscode-no-infinite-loop.js @@ -0,0 +1,28 @@ +'use strict' + +const { fetch } = require('../..') +const { createServer } = require('node:http') +const { once } = require('node:events') +const { test } = require('node:test') + +const { closeServerAsPromise } = require('../utils/node-http') + +test('Receiving a 401 status code should not cause infinite retry loop', async (t) => { + let requestCount = 0 + + const server = createServer({ joinDuplicateHeaders: true }, (req, res) => { + requestCount++ + console.log({ requestCount }) + res.statusCode = 401 + res.setHeader('WWW-Authenticate', 'Basic realm="test"') + res.end('Unauthorized') + }).listen(0) + + t.after(closeServerAsPromise(server)) + await once(server, 'listening') + + const response = await fetch(`http://localhost:${server.address().port}`) + + t.assert.strictEqual(response.status, 401) + t.assert.strictEqual(requestCount, 1, 'should only make one request, not retry infinitely') +}) From a24c66eac675ffe2caf0e42f75a60b1fda184217 Mon Sep 17 00:00:00 2001 From: Khafra Date: Fri, 23 Jan 2026 00:46:33 -0500 Subject: [PATCH 2/2] fixup --- test/fetch/401-statuscode-no-infinite-loop.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/fetch/401-statuscode-no-infinite-loop.js b/test/fetch/401-statuscode-no-infinite-loop.js index 4af1d08368f..1c808bb36cb 100644 --- a/test/fetch/401-statuscode-no-infinite-loop.js +++ b/test/fetch/401-statuscode-no-infinite-loop.js @@ -4,6 +4,7 @@ const { fetch } = require('../..') const { createServer } = require('node:http') const { once } = require('node:events') const { test } = require('node:test') +const assert = require('node:assert') const { closeServerAsPromise } = require('../utils/node-http') @@ -21,8 +22,5 @@ test('Receiving a 401 status code should not cause infinite retry loop', async ( t.after(closeServerAsPromise(server)) await once(server, 'listening') - const response = await fetch(`http://localhost:${server.address().port}`) - - t.assert.strictEqual(response.status, 401) - t.assert.strictEqual(requestCount, 1, 'should only make one request, not retry infinitely') + await assert.rejects(() => fetch(`http://localhost:${server.address().port}`)) })