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

Axios fetch adapter ignores maxRedirects:0, so a 302 from an attacker-influenced URL is followed to internal services, leaking responses and hitting state-changing endpoints.

- Payout: Research
- Severity: high
- Program: Axios
- Found by: mcdubhghlas
- Disclosed: 2026-09-30
- CVE: CVE-2026-101907
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-r4gj-5m52-g5wh
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-r4gj-5m52-g5wh
- Tags: area:web domain:oss domain:web impact:data-leak impact:integrity impact:internal-access kind:research platform:ghsa sev:high tech:nodejs technique:source-review vuln:misconfiguration vuln:ssrf

**Target:** npm axios fetch adapter (lib/adapters/fetch.js)
**Root cause:** The fetch adapter destructures config from resolveConfig but never reads maxRedirects, and builds fetch options without a `redirect` key, so the Fetch API default `redirect: 'follow'` applies. The HTTP adapter enforces the cap via follow-redirects.
**Steps:**
1. Start server A returning `302 Location: http://127.0.0.1:13802/internal/secrets` on `/api/data` and `302 Location: http://127.0.0.1:13802/internal/admin/config` on `/api/change-config`.
2. Start server B serving `INTERNAL`/secrets on `/internal/secrets` and mutating state on `/internal/admin/config`.
3. Control: `await axios.get(serverA, { adapter: 'http', maxRedirects: 0 })` throws `ERR_BAD_RESPONSE`, internal hit count stays 0.
4. Bypass: `await axios.get(serverA, { adapter: 'fetch', maxRedirects: 0 })` follows the 302 and returns the internal response.
5. Bypass 2: same with the change-config URL reaches and mutates the internal admin endpoint.
**How found:** Noticed the fetch adapter's destructuring of resolveConfig omits maxRedirects and the resolvedOptions object has no `redirect` key, then compared adapter behavior against the documented maxRedirects:0 SSRF-guard pattern and the threat model's T-R2 credential-stripping mitigation.
**Exploitation:** Attacker controls the initial URL or an open redirect on the target; the app issues the request with maxRedirects:0 expecting redirects to be blocked. The fetch adapter silently follows the 302 to an internal host (e.g. 169.254.169.254 metadata, Redis, admin APIs), returning the internal response to the caller or triggering state changes, with no error or warning.
**Escalation:** Beyond read-only SSRF, redirects to state-changing internal endpoints succeed, giving integrity impact in addition to confidentiality.
**Insight:** When a library ships multiple adapters/transports, security options enforced in one are often silently dropped in another; diff the config destructuring of each adapter against the documented security-relevant options and test the same guard across every adapter selection path (runtime-default vs explicit).

## Proof of concept (copied exactly from the original report)

**Minimal two-line adapter comparison**

```
await axios.get(serverA, { adapter: 'http', maxRedirects: 0 });  // throws
await axios.get(serverA, { adapter: 'fetch', maxRedirects: 0 }); // returns INTERNAL
```

