VYPR
High severity7.5NVD Advisory· Published Jun 4, 2026· Updated Jun 4, 2026

Allocation of Resources Without Limits or Throttling in Axios

CVE-2026-44488

Description

Summary

Axios versions 1.7.0 through 1.15.x did not enforce configured request and response size limits when requests were sent with the fetch adapter. Applications that selected adapter: 'fetch', or ran in environments where axios resolved to the fetch adapter, could receive or send bodies larger than maxContentLength or maxBodyLength despite those limits being explicitly configured.

This can cause resource exhaustion in server-side usage when a malicious or compromised server returns an oversized response, when an attacker can supply a large data: URL, or when an application forwards attacker-controlled request bodies through axios while relying on maxBodyLength as a boundary.

Impact

The impact is availability-only. Affected applications may process, buffer, or transmit data beyond the configured limit, potentially exhausting memory, CPU, or network resources.

This does not affect axios’s default unlimited behaviour by itself: maxContentLength and maxBodyLength default to -1. The vulnerability exists when an application has configured finite limits and expects axios to enforce them.

Server-side runtimes are the primary concern. Browser impact is generally constrained by the browser process and browser fetch behavior, and should not be described as server process exhaustion.

Affected

Functionality

Affected functionality includes requests using the built-in fetch adapter with finite maxContentLength or maxBodyLength values.

Relevant configurations include:

  • adapter: 'fetch'
  • adapter: ['fetch', ...] when fetch is selected
  • environments where neither xhr nor http is available and axios falls back to fetch
  • custom fetch environments configured through env.fetch

Unaffected functionality includes:

  • Node.js default http adapter enforcement
  • versions before the fetch adapter was introduced
  • configurations that do not rely on finite axios size limits

Technical

Details

In vulnerable versions, lib/adapters/fetch.js destructured request config without maxContentLength or maxBodyLength. The adapter dispatched fetch() and then materialized the response through text(), arrayBuffer(), blob(), or related resolvers without checking the configured response limit.

The fix in e5540dc added:

  • maxContentLength and maxBodyLength reads in lib/adapters/fetch.js
  • upfront data: URL decoded-size checks
  • outbound body-size checks before dispatch
  • Content-Length response pre-checks
  • streaming response enforcement
  • fallback checks for environments without ReadableStream
  • regression tests in tests/unit/adapters/fetch.test.js

Proof of

Concept of Attack

import http from 'node:http';
import axios from 'axios';

const server = http.createServer((req, res) => {
  let received = 0;

  req.on('data', chunk => {
    received += chunk.length;
  });

  req.on('end', () => {
    res.end(JSON.stringify({ received }));
  });
});

await new Promise(resolve => server.listen(0, resolve));
const url = `http://127.0.0.1:${server.address().port}/`;

await axios.post(url, 'A'.repeat(2 * 1024 * 1024), {
  adapter: 'fetch',
  maxBodyLength: 1024
});

// Vulnerable versions succeed and the server receives 2097152 bytes.
// Fixed versions reject with ERR_BAD_REQUEST.

server.close();

Workarounds

Use the Node.js http adapter for server-side requests where finite size limits are security-relevant.

Validate or cap attacker-controlled request bodies before passing them to axios.

Reject or strictly allowlist attacker-controlled URL schemes, especially data: URLs, before calling axios.

Original Report

Summary

When Axios is used with adapter: 'fetch', configured body/response size limits are not enforced. This allows oversized uploads/downloads (including data: URLs) despite explicit limits, which can lead to memory/resource exhaustion in server-side usage.

### Details maxBodyLength and maxContentLength are not applied in the fetch adapter flow: - lib/adapters/fetch.js (146-160): config destructuring does not include these controls. - lib/adapters/fetch.js (220-234): request is dispatched with fetch() without request-size enforcement. - lib/adapters/fetch.js (267-283): response is materialized via text(), arrayBuffer(), blob(), etc. without response-size checks. By contrast, the HTTP adapter enforces both limits.

PoC

Environment: - Axios main at commit f7a4ee2 - Node v24.2.0

Steps: 1. Start an HTTP server that counts received bytes and echoes {received}. 2. Send 2 MiB with: - adapter: 'fetch' - maxBodyLength: 1024 3. Request a 4 KiB data: URL with: - adapter: 'fetch' - maxContentLength: 16

Expected secure behavior: both requests rejected. Observed: - Upload: success, server received 2097152 - data: response: success, length 4096

Impact

Type: DoS / resource exhaustion due to limit bypass. Impacted: applications using Axios fetch adapter as a server-side security control boundary for untrusted request/response sizes.

---

Affected products

2

Patches

2
d8165e9f2c4a

fix: incorrect assumption on test (#10796)

https://github.com/axios/axiosJayApr 22, 2026via ghsa-ref
2 files changed · +24 7
  • lib/adapters/fetch.js+1 0 modified
    @@ -312,6 +312,7 @@ const factory = (env) => {
     
           if (
             supportsResponseStream &&
    +        response.body &&
             (onDownloadProgress || hasMaxContentLength || (isStreamResponse && unsubscribe))
           ) {
             const options = {};
    
  • lib/helpers/estimateDataURLDecodedBytes.js+23 7 modified
    @@ -73,12 +73,28 @@ export default function estimateDataURLDecodedBytes(url) {
         return Buffer.byteLength(body, 'utf8');
       }
     
    -  // Browser/worker fallback: use TextEncoder when available, else fall back to
    -  // raw string length as an upper-bound heuristic. Both are safe for a DoS
    -  // guard (over-counting only makes the check stricter for non-ASCII content).
    -  if (typeof TextEncoder === 'function') {
    -    return new TextEncoder().encode(body).byteLength;
    +  // Compute UTF-8 byte length directly from UTF-16 code units without allocating
    +  // a byte buffer (TextEncoder.encode would defeat the DoS guard on large bodies).
    +  // Using body.length here would undercount non-ASCII (e.g. '€' is 1 code unit
    +  // but 3 UTF-8 bytes).
    +  let bytes = 0;
    +  for (let i = 0, len = body.length; i < len; i++) {
    +    const c = body.charCodeAt(i);
    +    if (c < 0x80) {
    +      bytes += 1;
    +    } else if (c < 0x800) {
    +      bytes += 2;
    +    } else if (c >= 0xd800 && c <= 0xdbff && i + 1 < len) {
    +      const next = body.charCodeAt(i + 1);
    +      if (next >= 0xdc00 && next <= 0xdfff) {
    +        bytes += 4;
    +        i++;
    +      } else {
    +        bytes += 3;
    +      }
    +    } else {
    +      bytes += 3;
    +    }
       }
    -
    -  return body.length;
    +  return bytes;
     }
    
e5540dcafe42

fix: fetch adaptor is not enforcing max body or content length (#10795)

https://github.com/axios/axiosJayApr 22, 2026via ghsa-ref
3 files changed · +280 3
  • lib/adapters/fetch.js+103 2 modified
    @@ -11,6 +11,7 @@ import {
     } from '../helpers/progressEventReducer.js';
     import resolveConfig from '../helpers/resolveConfig.js';
     import settle from '../core/settle.js';
    +import estimateDataURLDecodedBytes from '../helpers/estimateDataURLDecodedBytes.js';
     
     const DEFAULT_CHUNK_SIZE = 64 * 1024;
     
    @@ -163,8 +164,13 @@ const factory = (env) => {
           headers,
           withCredentials = 'same-origin',
           fetchOptions,
    +      maxContentLength,
    +      maxBodyLength,
         } = resolveConfig(config);
     
    +    const hasMaxContentLength = utils.isNumber(maxContentLength) && maxContentLength > -1;
    +    const hasMaxBodyLength = utils.isNumber(maxBodyLength) && maxBodyLength > -1;
    +
         let _fetch = envFetch || fetch;
     
         responseType = responseType ? (responseType + '').toLowerCase() : 'text';
    @@ -186,6 +192,41 @@ const factory = (env) => {
         let requestContentLength;
     
         try {
    +      // Enforce maxContentLength for data: URLs up-front so we never materialize
    +      // an oversized payload. The HTTP adapter applies the same check (see http.js
    +      // "if (protocol === 'data:')" branch).
    +      if (hasMaxContentLength && typeof url === 'string' && url.startsWith('data:')) {
    +        const estimated = estimateDataURLDecodedBytes(url);
    +        if (estimated > maxContentLength) {
    +          throw new AxiosError(
    +            'maxContentLength size of ' + maxContentLength + ' exceeded',
    +            AxiosError.ERR_BAD_RESPONSE,
    +            config,
    +            request
    +          );
    +        }
    +      }
    +
    +      // Enforce maxBodyLength against the outbound request body before dispatch.
    +      // Mirrors http.js behavior (ERR_BAD_REQUEST / 'Request body larger than
    +      // maxBodyLength limit'). Skip when the body length cannot be determined
    +      // (e.g. a live ReadableStream supplied by the caller).
    +      if (hasMaxBodyLength && method !== 'get' && method !== 'head') {
    +        const outboundLength = await resolveBodyLength(headers, data);
    +        if (
    +          typeof outboundLength === 'number' &&
    +          isFinite(outboundLength) &&
    +          outboundLength > maxBodyLength
    +        ) {
    +          throw new AxiosError(
    +            'Request body larger than maxBodyLength limit',
    +            AxiosError.ERR_BAD_REQUEST,
    +            config,
    +            request
    +          );
    +        }
    +      }
    +
           if (
             onUploadProgress &&
             supportsRequestStream &&
    @@ -252,10 +293,27 @@ const factory = (env) => {
             ? _fetch(request, fetchOptions)
             : _fetch(url, resolvedOptions));
     
    +      // Cheap pre-check: if the server honestly declares a content-length that
    +      // already exceeds the cap, reject before we start streaming.
    +      if (hasMaxContentLength) {
    +        const declaredLength = utils.toFiniteNumber(response.headers.get('content-length'));
    +        if (declaredLength != null && declaredLength > maxContentLength) {
    +          throw new AxiosError(
    +            'maxContentLength size of ' + maxContentLength + ' exceeded',
    +            AxiosError.ERR_BAD_RESPONSE,
    +            config,
    +            request
    +          );
    +        }
    +      }
    +
           const isStreamResponse =
             supportsResponseStream && (responseType === 'stream' || responseType === 'response');
     
    -      if (supportsResponseStream && (onDownloadProgress || (isStreamResponse && unsubscribe))) {
    +      if (
    +        supportsResponseStream &&
    +        (onDownloadProgress || hasMaxContentLength || (isStreamResponse && unsubscribe))
    +      ) {
             const options = {};
     
             ['status', 'statusText', 'headers'].forEach((prop) => {
    @@ -272,8 +330,24 @@ const factory = (env) => {
                 )) ||
               [];
     
    +        let bytesRead = 0;
    +        const onChunkProgress = (loadedBytes) => {
    +          if (hasMaxContentLength) {
    +            bytesRead = loadedBytes;
    +            if (bytesRead > maxContentLength) {
    +              throw new AxiosError(
    +                'maxContentLength size of ' + maxContentLength + ' exceeded',
    +                AxiosError.ERR_BAD_RESPONSE,
    +                config,
    +                request
    +              );
    +            }
    +          }
    +          onProgress && onProgress(loadedBytes);
    +        };
    +
             response = new Response(
    -          trackStream(response.body, DEFAULT_CHUNK_SIZE, onProgress, () => {
    +          trackStream(response.body, DEFAULT_CHUNK_SIZE, onChunkProgress, () => {
                 flush && flush();
                 unsubscribe && unsubscribe();
               }),
    @@ -288,6 +362,33 @@ const factory = (env) => {
             config
           );
     
    +      // Fallback enforcement for environments without ReadableStream support
    +      // (legacy runtimes). Detect materialized size from typed output; skip
    +      // streams/Response passthrough since the user will read those themselves.
    +      if (hasMaxContentLength && !supportsResponseStream && !isStreamResponse) {
    +        let materializedSize;
    +        if (responseData != null) {
    +          if (typeof responseData.byteLength === 'number') {
    +            materializedSize = responseData.byteLength;
    +          } else if (typeof responseData.size === 'number') {
    +            materializedSize = responseData.size;
    +          } else if (typeof responseData === 'string') {
    +            materializedSize =
    +              typeof TextEncoder === 'function'
    +                ? new TextEncoder().encode(responseData).byteLength
    +                : responseData.length;
    +          }
    +        }
    +        if (typeof materializedSize === 'number' && materializedSize > maxContentLength) {
    +          throw new AxiosError(
    +            'maxContentLength size of ' + maxContentLength + ' exceeded',
    +            AxiosError.ERR_BAD_RESPONSE,
    +            config,
    +            request
    +          );
    +        }
    +      }
    +
           !isStreamResponse && unsubscribe && unsubscribe();
     
           return await new Promise((resolve, reject) => {
    
  • lib/helpers/estimateDataURLDecodedBytes.js+12 1 modified
    @@ -69,5 +69,16 @@ export default function estimateDataURLDecodedBytes(url) {
         return bytes > 0 ? bytes : 0;
       }
     
    -  return Buffer.byteLength(body, 'utf8');
    +  if (typeof Buffer !== 'undefined' && typeof Buffer.byteLength === 'function') {
    +    return Buffer.byteLength(body, 'utf8');
    +  }
    +
    +  // Browser/worker fallback: use TextEncoder when available, else fall back to
    +  // raw string length as an upper-bound heuristic. Both are safe for a DoS
    +  // guard (over-counting only makes the check stricter for non-ASCII content).
    +  if (typeof TextEncoder === 'function') {
    +    return new TextEncoder().encode(body).byteLength;
    +  }
    +
    +  return body.length;
     }
    
  • tests/unit/adapters/fetch.test.js+165 0 modified
    @@ -732,6 +732,171 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
         });
       });
     
    +  describe('size limits (GHSA-777c-7fjr-54vf)', () => {
    +    it('should reject an outbound body that exceeds maxBodyLength with ERR_BAD_REQUEST', async () => {
    +      const server = await startHTTPServer(
    +        (req, res) => {
    +          res.end('ok');
    +        },
    +        { port: SERVER_PORT }
    +      );
    +
    +      try {
    +        await assert.rejects(
    +          fetchAxios.post(`${LOCAL_SERVER_URL}/`, 'A'.repeat(2048), {
    +            maxBodyLength: 1024,
    +          }),
    +          (err) => {
    +            assert.strictEqual(err.code, 'ERR_BAD_REQUEST');
    +            assert.match(err.message, /Request body larger than maxBodyLength limit/);
    +            return true;
    +          }
    +        );
    +      } finally {
    +        await stopHTTPServer(server);
    +      }
    +    });
    +
    +    it('should reject a response whose Content-Length exceeds maxContentLength with ERR_BAD_RESPONSE', async () => {
    +      const payload = 'A'.repeat(8 * 1024);
    +      const server = await startHTTPServer(
    +        (req, res) => {
    +          res.setHeader('Content-Length', Buffer.byteLength(payload));
    +          res.end(payload);
    +        },
    +        { port: SERVER_PORT }
    +      );
    +
    +      try {
    +        await assert.rejects(
    +          fetchAxios.get(`${LOCAL_SERVER_URL}/`, {
    +            maxContentLength: 1024,
    +          }),
    +          (err) => {
    +            assert.strictEqual(err.code, 'ERR_BAD_RESPONSE');
    +            assert.match(err.message, /maxContentLength size of 1024 exceeded/);
    +            return true;
    +          }
    +        );
    +      } finally {
    +        await stopHTTPServer(server);
    +      }
    +    });
    +
    +    it('should reject a chunked response that exceeds maxContentLength during streaming', async () => {
    +      const server = await startHTTPServer(
    +        (req, res) => {
    +          // Omit content-length so the cheap pre-check cannot fire; force
    +          // the stream-based enforcement path.
    +          res.setHeader('Transfer-Encoding', 'chunked');
    +          const chunk = 'B'.repeat(1024);
    +          let sent = 0;
    +          const writeNext = () => {
    +            if (sent >= 8) {
    +              return res.end();
    +            }
    +            sent++;
    +            res.write(chunk, writeNext);
    +          };
    +          writeNext();
    +        },
    +        { port: SERVER_PORT }
    +      );
    +
    +      try {
    +        await assert.rejects(
    +          fetchAxios.get(`${LOCAL_SERVER_URL}/`, {
    +            maxContentLength: 512,
    +          }),
    +          (err) => {
    +            assert.strictEqual(err.code, 'ERR_BAD_RESPONSE');
    +            assert.match(err.message, /maxContentLength size of 512 exceeded/);
    +            return true;
    +          }
    +        );
    +      } finally {
    +        await stopHTTPServer(server);
    +      }
    +    });
    +
    +    it('should reject a data: URL whose decoded size exceeds maxContentLength (base64)', async () => {
    +      const payload = 'A'.repeat(4096);
    +      const dataUrl = 'data:application/octet-stream;base64,' + Buffer.from(payload).toString('base64');
    +
    +      // Use a dedicated instance without baseURL — combineURLs would otherwise
    +      // prepend baseURL to a data: URL and neutralise the pre-check.
    +      const bareAxios = axios.create({ adapter: 'fetch' });
    +
    +      await assert.rejects(
    +        bareAxios.get(dataUrl, { maxContentLength: 16 }),
    +        (err) => {
    +          assert.strictEqual(err.code, 'ERR_BAD_RESPONSE');
    +          assert.match(err.message, /maxContentLength size of 16 exceeded/);
    +          return true;
    +        }
    +      );
    +    });
    +
    +    it('should reject a data: URL whose body size exceeds maxContentLength (non-base64)', async () => {
    +      const dataUrl = 'data:text/plain,' + 'X'.repeat(4096);
    +
    +      const bareAxios = axios.create({ adapter: 'fetch' });
    +
    +      await assert.rejects(
    +        bareAxios.get(dataUrl, { maxContentLength: 16 }),
    +        (err) => {
    +          assert.strictEqual(err.code, 'ERR_BAD_RESPONSE');
    +          assert.match(err.message, /maxContentLength size of 16 exceeded/);
    +          return true;
    +        }
    +      );
    +    });
    +
    +    it('should allow a response at or below maxContentLength', async () => {
    +      const payload = 'ok';
    +      const server = await startHTTPServer(
    +        (req, res) => {
    +          res.end(payload);
    +        },
    +        { port: SERVER_PORT }
    +      );
    +
    +      try {
    +        const { data } = await fetchAxios.get(`${LOCAL_SERVER_URL}/`, {
    +          maxContentLength: 1024,
    +        });
    +        assert.strictEqual(data, payload);
    +      } finally {
    +        await stopHTTPServer(server);
    +      }
    +    });
    +
    +    it('should allow a body at or below maxBodyLength', async () => {
    +      const payload = 'hello';
    +      let received;
    +      const server = await startHTTPServer(
    +        (req, res) => {
    +          const chunks = [];
    +          req.on('data', (c) => chunks.push(c));
    +          req.on('end', () => {
    +            received = Buffer.concat(chunks).toString();
    +            res.end('ok');
    +          });
    +        },
    +        { port: SERVER_PORT }
    +      );
    +
    +      try {
    +        await fetchAxios.post(`${LOCAL_SERVER_URL}/`, payload, {
    +          maxBodyLength: 1024,
    +        });
    +        assert.strictEqual(received, payload);
    +      } finally {
    +        await stopHTTPServer(server);
    +      }
    +    });
    +  });
    +
       describe('capability probe cleanup', () => {
         it('should cancel the ReadableStream created during the request stream probe', () => {
           // The fetch adapter factory probes for request-stream support by creating
    

Vulnerability mechanics

Root cause

"The fetch adapter in Axios did not enforce configured request and response size limits."

Attack vector

An attacker can exploit this vulnerability by sending requests with bodies larger than the configured `maxBodyLength` or by requesting oversized responses, bypassing the `maxContentLength` limit. This is particularly concerning in server-side applications that rely on these limits as a security boundary. The attack can be facilitated by using the `fetch` adapter, either explicitly configured or as a fallback in environments lacking `xhr` or `http` adapters. Large `data:` URLs can also be used to trigger oversized responses [ref_id=1].

Affected code

In vulnerable versions, `lib/adapters/fetch.js` failed to destructure and apply `maxContentLength` and `maxBodyLength` from the request configuration. The adapter would dispatch the `fetch()` request and then materialize the response using methods like `text()`, `arrayBuffer()`, or `blob()` without checking against the configured response size limits [ref_id=1].

What the fix does

The fix, applied in commit `e5540dc`, modifies `lib/adapters/fetch.js` to correctly read and enforce `maxContentLength` and `maxBodyLength` configurations. It introduces upfront checks for `data:` URL sizes, outbound body sizes before dispatch, and `Content-Length` for responses. Additionally, it implements streaming response enforcement and fallback checks for environments lacking `ReadableStream` [ref_id=1].

Preconditions

  • configAxios must be configured with finite values for `maxContentLength` or `maxBodyLength`.
  • configThe `fetch` adapter must be selected, either explicitly via `adapter: 'fetch'`, as part of an adapter array where `fetch` is chosen, or implicitly in environments where `xhr` and `http` are unavailable.

Reproduction

```javascript import http from 'node:http'; import axios from 'axios';

const server = http.createServer((req, res) => { let received = 0;

req.on('data', chunk => { received += chunk.length; });

req.on('end', () => { res.end(JSON.stringify({ received })); }); });

await new Promise(resolve => server.listen(0, resolve)); const url = `http://127.0.0.1:${server.address().port}/`;

await axios.post(url, 'A'.repeat(2 * 1024 * 1024), { adapter: 'fetch', maxBodyLength: 1024 });

// Vulnerable versions succeed and the server receives 2097152 bytes. // Fixed versions reject with ERR_BAD_REQUEST.

server.close(); ```

Generated on Jun 4, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

1