GitHub Advisoryadvisory · npm
GHSA-x97p-jq2g-jp4f Axios: Prototype Pollution Gadget in axios toFormData Options
HIGH
Official page ↗
- Aliases
- CVE-2026-101909
- Ecosystem
- npm
- Packages
- axios
- Severity
- High
- CVSS vector
- CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N
- Weaknesses
- CWE-1321
- Reviewed
- 2026-09-30
Affected packages
• npm axios: >= 0.28.0, < 0.34.0 → fixed in 0.34.0
• npm axios: >= 1.15.1, < 1.20.0 → fixed in 1.20.0
Description
Summary
Axios form serialization reads `visitor`, `maxDepth`, `dots`, `indexes`, `metaTokens`, and `Blob` from an internal options object without own-property guards. When `Object.prototype` has been polluted elsewhere in the same process, those inherited values can change how axios serializes multipart and URL-encoded request bodies.
Axios does not create the prototype pollution source. This is a read-side gadget: axios turns an existing same-process pollution condition into altered request serialization or request failures.
Impact
The impact depends on which property is polluted and which axios serialization path the application uses.
Polluted `dots`, `indexes`, or `metaTokens` can change field names and cause the receiving service to parse different data than the caller intended. Polluted `maxDepth` can cause nested form submissions to throw `ERR_FORM_DATA_DEPTH_EXCEEDED`, producing request-level or service-level denial of service for affected workflows. Polluted `visitor` can execute as the serializer visitor if an attacker can place a function on `Object.prototype`, but that condition generally implies a stronger same-process code-execution or malicious-dependency primitive and should be described carefully.
Affected Functionality
Affected:
• `axios.toFormData()`.
• `transformRequest` paths that serialize plain objects to `multipart/form-data`.
• URL-encoded form serialization paths that rely on the same helper.
• `formSerializer` option defaults when the relevant properties are absent as own properties.
Not affected:
• JSON request bodies.
• Requests that do not invoke `toFormData()`.
• Processes where `Object.prototype` is not polluted.
Technical Details
`lib/helpers/toFormData.js` merges caller options with defaults using `utils.toFlatObject()`. When `options` is `undefined`, `toFlatObject()` returns the default object unchanged:
```js
{
metaTokens: true,
dots: false,
indexes: false
}
```
That default object has `Object.prototype` in its prototype chain. `toFormData()` then reads behavior-affecting values directly:
```js
const metaTokens = options.metaTokens;
const visitor = options.visitor || defaultVisitor;
const dots = options.dots;
const indexes = options.indexes;
const _Blob = options.Blob || (typeof Blob !== 'undefined' && Blob);
const maxDepth = options.maxDepth === undefined ? DEFAULT_FORM_DATA_MAX_DEPTH : options.maxDepth;
```
These reads can resolve inherited polluted properties.
Local code review confirmed the direct reads in `v1.18.1`. Tag checks show the option-based form serializer exists in `v0.28.0` and later; `maxDepth` appears in the `1.x` line from the form recursion fix.
Proof of Concept of Attack
Constrained local demonstration:
```js
Object.prototype.maxDepth = 1;
await axios.post(url, { a: { b: { c: 'value' } } }, {
headers: { 'Content-Type': 'multipart/form-data' }
});
```
Expected safe behavior is that the default max depth is used unless the caller sets an own `formSerializer.maxDepth`. Current behavior reads the inherited value and can throw `ERR_FORM_DATA_DEPTH_EXCEEDED`.
For serializer alteration, polluting `Object.prototype.dots = true` changes nested field naming from bracket notation to dot notation when the caller did not opt into that behavior.
Workarounds
Avoid serializing attacker-controlled objects as form data in a process with known prototype pollution. As a partial mitigation, callers can pass an own `formSerializer` object that sets explicit safe values for all relevant keys, including `visitor`, `maxDepth`, `dots`, `indexes`, `metaTokens`, and `Blob`.
Original report
Summary
_axios v1.18.1 contains a read-side prototype pollution gadget in its form data serialization logic. Six option properties (`visitor`, `maxDepth`, `dots`, `indexes`, `metaTokens`, `Blob`) are read from a plain JavaScript object that inherits from `Object.prototype` without `hasOwnProperty` guards. When `Object.prototype` has been polluted elsewhere in the process a common consequence of compromised transitive npm dependencies, these polluted values silently control axios' form serialization behavior._
_The highest-impact gadget is `visitor`: a polluted function on `Object.prototype.visitor` is invoked for every key-value pair during multipart and URL-encoded form serialization, receiving the value, key, path, and internal helper functions as arguments._
Details
Root Cause
_The attack chain has three steps:_
_Step 1: `formSerializer` is read safely, but `undefined` flows through_
_In `lib/defaults/index.js`, the default `transformRequest` function reads `formSerializer` from config using the `own()` helper, which enforces `hasOwnProp`:_
```js
const formSerializer = own(this, 'formSerializer');
```
_When the user does not explicitly configure `formSerializer`, this correctly returns `undefined`. That `undefined` is then passed as the `options` parameter to `toFormData()`:_
```js
return toFormData(data, _FormData && new _FormData(), formSerializer);
// ^^^^^^^^^^^^ undefined
```
_Step 2: `toFlatObject` returns a plain-object default_
_Inside `lib/helpers/toFormData.js`, `options` (which is `undefined`) is merged with defaults via `utils.toFlatObject()`:_
```js
options = utils.toFlatObject(
options, // undefined
{ metaTokens: true, dots: false, indexes: false }, // plain object literal
false,
function defined(option, source) {
return !utils.isUndefined(source[option]);
}
);
```
_`toFlatObject` has an early-return for null/undefined sources:_
```js
// lib/utils.js:607
if (sourceObj == null) return destObj;
```
_Since `options` is `undefined`, the function returns `destObj` unchanged — the plain object `{ metaTokens: true, dots: false, indexes: false }`. This object's prototype is `Object.prototype`._
_Step 3: Options are read without `hasOwnProp` guards_
_The six option properties are read directly from the plain object:_
```js
const metaTokens = options.metaTokens; // line 117
const visitor = options.visitor || defaultVisitor; // line 119
const dots = options.dots; // line 120
const indexes = options.indexes; // line 121
const _Blob = options.Blob || (typeof Blob !== 'undefined' && Blob); // line 122
const maxDepth = options.maxDepth === undefined // line 123
? DEFAULT_FORM_DATA_MAX_DEPTH
: options.maxDepth;
```
_None of these reads use `utils.hasOwnProp()`. Since the `options` object inherits from `Object.prototype`, any property set on `Object.prototype` by a compromised dependency is resolved through the prototype chain._
Why the Existing Defenses Didn't Catch This
_axios has extensive prototype pollution defenses. However, those defenses are all focused on the config object (created by `mergeConfig`, which returns `Object.create(null)`). The `toFormData` function creates its own internal options object that sits outside that boundary, and the 6 reads on that internal object were never audited._
PoC
Reproduction Steps
Environment
_Any environment with Node.js and npm. Tested on:_
_- Node.js v24.15.0, npm 11.13.0_
_- axios v1.18.1 (latest release at time of writing)_
Step 1: Create a fresh project
```bash
mkdir axios-pp-poc
cd axios-pp-poc
npm init -y
npm install [email protected]
```
Step 2: Create the PoC file
_Create `poc.mjs` with the following content:_
```js
import axios from 'axios';
import http from 'http';
// Simulate pollution from a compromised transitive dependency
let stolen = [];
Object.prototype.visitor = function(value, key, path, helpers) {
stolen.push({ key, value });
return helpers.defaultVisitor.call(this, value, key, path);
};
Object.prototyp…
Related
Alias · CVE (1)
- CVE-2026-101909 Axios is a promise-based HTTP client for the browser and Node.js. vulnerability
Weakness · CWE (1)
- CWE-1321 Improperly Controlled Modification of Object Prototype Attributes weakness
References
- https://github.com/axios/axios/security/advisories/GHSA-x97p-jq2g-jp4f web
- https://nvd.nist.gov/vuln/detail/CVE-2026-101909 advisory
- https://github.com/axios/axios/pull/11141 web
- https://github.com/axios/axios/commit/d19040bda7a8be2f82c3c6e1a5bc03917daee39a web
- https://github.com/axios/axios/commit/d29be181f85f6fe93397a07b1f69606d9622637b web
- https://github.com/axios/axios package
- https://github.com/axios/axios/releases/tag/v0.34.0 web
- https://github.com/axios/axios/releases/tag/v1.20.0 web