{"id":"CVE-2026-101908","aliases":["GHSA-vh66-26gq-q6x8"],"url":"https://o3.security/vulnerability/CVE-2026-101908","summary":"Axios: Prototype pollution gadget in fetch adapter can alter outbound requests","details":"## Summary\n\nAxios 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`.\n\nAxios does not create the prototype pollution source. This is a read-side gadget that becomes exploitable after same-process prototype pollution.\n\n## Impact\n\nAn 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.\n\nThe issue is specific to fetch-adapter behavior and does not affect Node HTTP adapter requests.\n\n## Affected Functionality\n\nAffected:\n\n- `adapter: 'fetch'`.\n- Runtime environments where the fetch adapter is selected.\n- Requests where `fetchOptions` is an object that does not have safe own values for sensitive fetch init fields.\n\nNot affected:\n\n- Node HTTP adapter.\n- Requests that do not use the fetch adapter.\n- Processes where `Object.prototype` is not polluted.\n\n## Technical Details\n\n`lib/adapters/fetch.js` builds:\n\n```js\nconst resolvedOptions = {\n  ...fetchOptions,\n  signal: composedSignal,\n  method: method.toUpperCase(),\n  headers: toByteStringHeaderObject(headers.normalize()),\n  body: data,\n  duplex: 'half',\n  credentials: isCredentialsSupported ? withCredentials : undefined,\n};\n\nrequest = isRequestSupported && new Request(url, resolvedOptions);\n\nlet response = await (isRequestSupported\n  ? _fetch(request, fetchOptions)\n  : _fetch(url, resolvedOptions));\n```\n\nThe 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`.\n\nLocal 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`.\n\n## Proof of Concept of Attack\n\nConstrained local demonstration:\n\n```js\nObject.prototype.headers = { Authorization: 'Bearer POLLUTED' };\ntry {\n  await axios.get(url, {\n    adapter: 'fetch',\n    headers: { 'X-Good': 'yes' },\n    fetchOptions: {}\n  });\n} finally {\n  delete Object.prototype.headers;\n}\n```\n\nExpected safe behavior is that the sanitized axios headers remain in force. Current behavior can use the inherited fetch init headers instead.\n\n## Workarounds\n\nUse 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.\n\n<details>\n  <summary><h3>Original report</h3></summary>\n  \nHello, 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.\n\nI was testing the fetch adapter with polluted prototype values and found that `Object.prototype.headers` can change the request axios sends.\n\nThe 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.\n\nI tested it with this:\n\n```js\nimport http from 'node:http';\nimport axios from 'axios';\n\nconst server = http.createServer((req, res) => {\n  res.end(JSON.stringify({\n    authorization: req.headers.authorization || null,\n    xGood: req.headers['x-good'] || null\n  }));\n});\n\nawait new Promise(resolve => server.listen(0, '127.0.0.1', resolve));\nconst { port } = server.address();\n\nObject.prototype.headers = {\n  Authorization: 'Bearer POLLUTED'\n};\n\ntry {\n  const res = await axios.get(`http://127.0.0.1:${port}/`, {\n    adapter: 'fetch',\n    headers: { 'X-Good': 'yes' },\n    fetchOptions: {}\n  });\n\n  console.log(res.data);\n} finally {\n  delete Object.prototype.headers;\n  server.close();\n}\n```\n\nThe result I get is:\n\n```json\n{\n  \"authorization\": \"Bearer POLLUTED\",\n  \"xGood\": null\n}\n```\n\nSo the polluted Authorization header is sent, and the normal axios header is not.\n\nChanging the fetch call to pass the already resolved options fixes it for me:\n\n```diff\n- _fetch(request, fetchOptions)\n+ _fetch(request, resolvedOptions)\n```\n\n`resolvedOptions` already includes `...fetchOptions`, so custom fetch options should still work, while headers, method, body, and signal stay as clean own values.\n</details>\n\n---","published":"2026-09-28T17:38:55.303Z","modified":"2026-10-21T02:54:27.478666876Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":null,"affectedPackages":[{"ecosystem":"npm","name":"axios","fixedVersion":"1.20.0"}],"fix":{"url":"https://github.com/axios/axios/commit/d19040bda7a8be2f82c3c6e1a5bc03917daee39a","label":"axios/axios@d19040b"},"references":[{"type":"WEB","url":"https://github.com/axios/axios/releases/tag/v1.20.0"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/101xxx/CVE-2026-101908.json"},{"type":"ADVISORY","url":"https://github.com/axios/axios/security/advisories/GHSA-vh66-26gq-q6x8"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-101908"},{"type":"FIX","url":"https://github.com/axios/axios/commit/d19040bda7a8be2f82c3c6e1a5bc03917daee39a"},{"type":"FIX","url":"https://github.com/axios/axios/pull/11141"},{"type":"PACKAGE","url":"https://github.com/axios/axios"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-21T02:54:27.478666876Z"}}