GitHub Security Advisory: GHSA-pf86-5x62-jrwf
Package: axios (NPM)
Severity: HIGH (CVSS 7.4)
CWE: CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
Vulnerable versions: <= 0.31.0
Patched in: 0.31.1

Summary: Axios: Prototype Pollution Gadgets - Response Tampering, Data Exfiltration, and Request Hijacking

Details:
## Summary

When `Object.prototype` has been polluted by any co-dependency with keys that axios reads without a `hasOwnProperty` guard, an attacker can (a) silently intercept and modify every JSON response before the application sees it, or (b) fully hijack the underlying HTTP transport, gaining access to request credentials, headers, and body. The precondition is prototype pollution from a separate source in the same process -- lodash < 4.17.21, or any of several other common npm packages with known PP vectors. The two gadgets confirmed here work independently.

---

## Background: how mergeConfig builds the config object

Every axios request goes through `Axios._request` in [`lib/core/Axios.js#L76`](https://github.com/axios/axios/blob/v1.13.6/lib/core/Axios.js#L76):

```js
config = mergeConfig(this.defaults, config);
```

Inside `mergeConfig`, the merged config is built as a plain `{}` object ([`lib/core/mergeConfig.js#L20`](https://github.com/axios/axios/blob/v1.13.6/lib/core/mergeConfig.js#L20)):

```js
const config = {};
```

A plain `{}` inherits from `Object.prototype`. `mergeConfig` only iterates `Object.keys({ ...config1, ...config2 })` ([line 99](https://github.com/axios/axios/blob/v1.13.6/lib/core/mergeConfig.js#L99)), which is a spread of own properties. Any key that is absent from both `this.defaults` and the per-request config will never be set as an own property on the merged config. Reading that key later on the merged config falls through to `Object.prototype`. That is the root mechanism behind all gadgets below.

---

## Gadget 1: parseReviver -- response tampering and exfiltration

**Introduced in:** v1.12.0 (commit 2a97634, PR #5926)
**Affected range:** >= 1.12.0, <= 1.13.6

### Root cause

The default `transformResponse` function calls [`JSON.parse(data, this.parseReviver)`](https://github.com/axios/axios/blob/v1.13.6/lib/defaults/index.js#L124):

```js
return JSON.parse(data, this.parseReviver);
```

`this` is the merged config. `parseReviver` is not present in `defaults` and is not in the `mergeMap` inside `mergeConfig`. It is never set as an own property on the merged config. Accessing `this.parseReviver` therefore walks the prototype chain.

The call fires by default on every string response body because [`lib/defaults/transitional.js#L5`](https://github.com/axios/axios/blob/v1.13.6/lib/defaults/transitional.js#L5) sets:

```js
forcedJSONParsing: true,
```

which activates the JSON parse path unconditionally when `responseType` is unset.

`JSON.parse(text, reviver)` calls the reviver for every key-value pair in the parsed result, bottom-up. The reviver's return value is what the caller receives. An attacker-controlled reviver can both observe every key-value pair and silently replace values.

There is no interaction with `assertOptions` here. The `assertOptions` call in `Axios._request` ([line 119](https://github.com/axios/axios/blob/v1.13.6/lib/core/Axios.js#L119)) iterates `Object.keys(config)`, and since `parseReviver`