From e8904af03385b040e53f1263a444e825db4335d9 Mon Sep 17 00:00:00 2001 From: Jay Date: Sat, 18 Apr 2026 16:23:39 +0200 Subject: [PATCH] fix: stream response bypassed max content length (#10754) * fix: enforce max body length when max redirects is 0 * fix: stream response bypassed max content length --- lib/adapters/http.js | 24 ++++++++++++ tests/unit/adapters/http.test.js | 64 ++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/lib/adapters/http.js b/lib/adapters/http.js index 0e8062e2..5e596b3c 100755 --- a/lib/adapters/http.js +++ b/lib/adapters/http.js @@ -795,6 +795,30 @@ export default isHttpAdapterSupported && }; if (responseType === 'stream') { + // Enforce maxContentLength on streamed responses; previously this + // was applied only to buffered responses. See GHSA-vf2m-468p-8v99. + if (config.maxContentLength > -1) { + const limit = config.maxContentLength; + const source = responseStream; + async function* enforceMaxContentLength() { + let totalResponseBytes = 0; + for await (const chunk of source) { + totalResponseBytes += chunk.length; + if (totalResponseBytes > limit) { + throw new AxiosError( + 'maxContentLength size of ' + limit + ' exceeded', + AxiosError.ERR_BAD_RESPONSE, + config, + lastRequest + ); + } + yield chunk; + } + } + responseStream = stream.Readable.from(enforceMaxContentLength(), { + objectMode: false, + }); + } response.data = responseStream; settle(resolve, reject, response); } else { diff --git a/tests/unit/adapters/http.test.js b/tests/unit/adapters/http.test.js index 7c2a7b94..f9421aba 100644 --- a/tests/unit/adapters/http.test.js +++ b/tests/unit/adapters/http.test.js @@ -988,6 +988,70 @@ describe('supports http with nodejs', () => { } }); + it('should enforce maxContentLength for streamed responses (GHSA-vf2m-468p-8v99)', async () => { + const size = 2 * 1024 * 1024; + const body = Buffer.alloc(size, 0x63); + const server = await startHTTPServer( + (req, res) => { + res.setHeader('Content-Type', 'application/octet-stream'); + res.end(body); + }, + { port: SERVER_PORT } + ); + + try { + const response = await axios.get(`http://localhost:${server.address().port}/`, { + responseType: 'stream', + maxContentLength: 1024, + }); + + let bytesRead = 0; + const err = await new Promise((resolve) => { + response.data.on('data', (chunk) => { bytesRead += chunk.length; }); + response.data.on('error', resolve); + response.data.on('end', () => resolve(null)); + }); + + assert.ok(err, 'stream should emit an error'); + assert.strictEqual(err.message, 'maxContentLength size of 1024 exceeded'); + assert.ok( + bytesRead <= 1024 * 64, + `stream should not deliver full payload; got ${bytesRead}` + ); + } finally { + await stopHTTPServer(server); + } + }); + + it('should allow streamed responses under maxContentLength', async () => { + const body = Buffer.alloc(512, 0x64); + const server = await startHTTPServer( + (req, res) => { + res.setHeader('Content-Type', 'application/octet-stream'); + res.end(body); + }, + { port: SERVER_PORT } + ); + + try { + const response = await axios.get(`http://localhost:${server.address().port}/`, { + responseType: 'stream', + maxContentLength: 1024, + }); + + const chunks = []; + await new Promise((resolve, reject) => { + response.data.on('data', (chunk) => chunks.push(chunk)); + response.data.on('error', reject); + response.data.on('end', resolve); + }); + + assert.strictEqual(Buffer.concat(chunks).length, body.length); + } finally { + await stopHTTPServer(server); + } + }); + it('should enforce maxBodyLength for streamed uploads with maxRedirects: 0 (GHSA-5c9x-8gcm-mpgx)', async () => { let bytesReceived = 0; const server = await startHTTPServer(