GitHub Advisoryadvisory · npm

GHSA-vh66-26gq-q6x8 Axios: Prototype pollution gadget in fetch adapter can alter outbound requests

MEDIUM Published 2026-09-30 Modified 2026-09-30 Official page ↗
Aliases
CVE-2026-101908
Ecosystem
npm
Packages
axios
Severity
Moderate
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:H/SA:N
Weaknesses
CWE-1321
Reviewed
2026-09-30

Affected packages

• npm axios: >= 1.7.0, < 1.20.0 → fixed in 1.20.0

Description

Summary Axios fetch adapter requests can be altered by inherited properties on `fetchOptions`. The adapter resolves method, headers, body, signal, and credentials into `resolvedOptions`, creates a `Request`, and then calls `fetch(request, fetchOptions)` instead of `fetch(request, resolvedOptions)`. In runtimes such as Node's undici-backed fetch, inherited `fetchOptions.headers` can override the headers already placed on the `Request`. Axios does not create the prototype pollution source. This is a read-side gadget that becomes exploitable after same-process prototype pollution. Impact An attacker with a prior prototype-pollution primitive can cause affected fetch-adapter requests to send attacker-controlled headers and drop caller-specified headers. This can affect authorization, cache behavior, metadata-service interactions, or application-specific header-based controls. The issue is specific to fetch-adapter behavior and does not affect Node HTTP adapter requests. Affected Functionality Affected: • `adapter: 'fetch'`. • Runtime environments where the fetch adapter is selected. • Requests where `fetchOptions` is an object that does not have safe own values for sensitive fetch init fields. Not affected: • Node HTTP adapter. • Requests that do not use the fetch adapter. • Processes where `Object.prototype` is not polluted. Technical Details `lib/adapters/fetch.js` builds: ```js const resolvedOptions = { ...fetchOptions, signal: composedSignal, method: method.toUpperCase(), headers: toByteStringHeaderObject(headers.normalize()), body: data, duplex: 'half', credentials: isCredentialsSupported ? withCredentials : undefined, }; request = isRequestSupported && new Request(url, resolvedOptions); let response = await (isRequestSupported ? _fetch(request, fetchOptions) : _fetch(url, resolvedOptions)); ``` The fallback path without `Request` uses `resolvedOptions`, but the `Request` path passes the original `fetchOptions` as the second argument to `fetch()`. That second argument can contain inherited properties from `Object.prototype`. Local verification on axios `1.18.1` set `Object.prototype.headers = { Authorization: 'Bearer POLLUTED' }` and called the fetch adapter with `headers: { 'X-Good': 'yes' }, fetchOptions: {}`. The loopback server received `Authorization: Bearer POLLUTED` and did not receive `X-Good`. Proof of Concept of Attack Constrained local demonstration: ```js Object.prototype.headers = { Authorization: 'Bearer POLLUTED' }; try { await axios.get(url, { adapter: 'fetch', headers: { 'X-Good': 'yes' }, fetchOptions: {} }); } finally { delete Object.prototype.headers; } ``` Expected safe behavior is that the sanitized axios headers remain in force. Current behavior can use the inherited fetch init headers instead. Workarounds Use the Node HTTP adapter for security-sensitive server-side requests until fixed. If the fetch adapter must be used, avoid passing empty `fetchOptions` objects in processes where prototype pollution is possible, and set explicit safe own values for fetch init fields. Original report Hello, I’m not completely sure if this is something you’d consider a security issue, since it depends on prototype pollution happening somewhere else first, but I wanted to report it just in case. I was testing the fetch adapter with polluted prototype values and found that `Object.prototype.headers` can change the request axios sends. The issue seems to be in `lib/adapters/fetch.js`. Axios creates a `Request` with the resolved headers/method/body, but then sends it with `fetch(request, fetchOptions)`. With undici, if `fetchOptions` doesn’t have its own headers, an inherited `Object.prototype.headers` value can be used during the final fetch call. I tested it with this: ```js import http from 'node:http'; import axios from 'axios'; const server = http.createServer((req, res) => { res.end(JSON.stringify({ authorization: req.headers.authorization || null, xGood: req.headers['x-good'] || null })); }); await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); const { port } = server.address(); Object.prototype.headers = { Authorization: 'Bearer POLLUTED' }; try { const res = await axios.get(`http://127.0.0.1:${port}/`, { adapter: 'fetch', headers: { 'X-Good': 'yes' }, fetchOptions: {} }); console.log(res.data); } finally { delete Object.prototype.headers; server.close(); } ``` The result I get is: ```json { "authorization": "Bearer POLLUTED", "xGood": null } ``` So the polluted Authorization header is sent, and the normal axios header is not. Changing the fetch call to pass the already resolved options fixes it for me: ```diff • _fetch(request, fetchOptions) + _fetch(request, resolvedOptions) ``` `resolvedOptions` already includes `...fetchOptions`, so custom fetch options should still work, while headers, method, body, and signal stay as clean own values. ---

Related

Alias · CVE (1)

  • CVE-2026-101908 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