GitHub Advisoryadvisory · npm

GHSA-r4gj-5m52-g5wh Axios: maxRedirects: 0 is not enforced by the fetch adapter, allowing redirect-based SSRF

HIGH Published 2026-09-30 Modified 2026-09-30 Official page ↗
Aliases
CVE-2026-101907
Ecosystem
npm
Packages
axios
Severity
High
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N
Weaknesses
CWE-441, CWE-601
Reviewed
2026-09-30

Affected packages

• npm axios: >= 1.17.0, < 1.20.0 → fixed in 1.20.0

Description

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:/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. Original report 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({ reached: 'state-changing internal admin endpoint', changed: true, internalConfig, })); } res.writeHead(404); res.end(); }); await Promise.all([ new Promise((resolve, reje…

Related

Alias · CVE (1)

  • CVE-2026-101907 Axios is a promise-based HTTP client for the browser and Node.js. vulnerability

Weakness · CWE (2)

  • CWE-441 Unintended Proxy or Intermediary weakness
  • CWE-601 URL Redirection to Untrusted Site weakness

References