You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Axios 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.
This 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 || {}.
Impact
An 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.
The 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.
This 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.
Affected Functionality
Affected functionality:
Node HTTP adapter Basic auth handling in lib/adapters/http.js.
Browser, web worker, React Native, and fetch shared resolver Basic auth handling in lib/helpers/resolveConfig.js.
Requests where config.auth is an own object but username and/or password are absent own properties.
Unaffected or not accepted as core impact:
Requests with no own auth object after mergeConfig().
Requests with own auth.username and auth.password values.
Normal axios request flow for inherited top-level params / paramsSerializer after the null-prototype mergeConfig() hardening.
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.
Technical Details
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.
In lib/adapters/http.js, axios correctly reads the top-level auth value through own('auth'), but then reads subfields directly:
The base64 value decodes to victim-user:victim-password-leaked.
Workarounds
Avoid passing empty or partial auth objects. Only set auth when the application has own username and password values.
Applications that merge untrusted input should filter __proto__, constructor, and prototype, and should read optional user options with own-property checks rather than opts.auth || {}.
Where a wrapper must materialize optional auth, use a null-prototype object or explicitly copy only own fields.
Original Report
Summary
After GHSA-q8qp-cvcw-x6jj / PR #10779 (shipped in v1.15.2) and the further proxy-side hardening in PR #10833 (merged 2026-05-02), the top-levelconfig.auth and the proxy authsub-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.
When 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).
Reproduces against axiosmain HEAD (34723be, dated 2026-05-24)
as well as the released v1.16.1.
constconfigAuth=own('auth');// ← top-level guard OKif(configAuth){constusername=configAuth.username||'';// ← reads .username on the inherited chainconstpassword=configAuth.password||'';// ← reads .password on the inherited chainauth=username+':'+password;}
own('auth') correctly applies hasOwnProp to the top-level auth
key. But once configAuth is the empty object the caller passed
(auth: {}), configAuth.username walks the prototype chain and
picks up Object.prototype.username.
Contrast with the proxy-auth path that PR #10833 fixed (lines 322–324):
newConfig.url=buildURL(buildFullPath(baseURL,url,allowAbsoluteUrls),config.params,// ← direct read, not through own()config.paramsSerializer// ← direct read, not through own());
This third site is already proposed for fix in openPR #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.
PoC
This PoC contains zero direct Object.prototype.x = y writes. The
pollution flows entirely from attacker-shaped JSON through a real
deep-merge utility (defaults-deep@0.2.4, ~50k weekly downloads,
still walks constructor.prototype). A hand-rolled deep merge —
the canonical insecure backend pattern — exhibits the same pollution
via __proto__ and is more common in real codebases than any named
utility.
#!/usr/bin/env node
'use strict';consthttp=require('node:http');constaxios=require('axios');constdefaultsDeep=require('defaults-deep');// Defensive: scrub any prior pollutionconstPROTO_KEYS=['username','password','params','paramsSerializer'];functionscrub(){for(constkofPROTO_KEYS){try{deleteObject.prototype[k];}catch(_){}}}scrub();// 1) Attacker input — what JSON.parse(req.body) would yield from an HTTP POSTconstattackerBody=JSON.parse(`{ "constructor": { "prototype": { "username": "victim-user", "password": "victim-password-leaked", "params": {"leak": "ATTACKER_QUERY_TOKEN"} } }}`);// 2) Realistic application pattern: merge user options into defaultsconstappDefaults={timeout: 5000};defaultsDeep(appDefaults,attackerBody);// After this line:// Object.prototype.username === "victim-user"// Object.prototype.password === "victim-password-leaked"// Object.prototype.params === { leak: "ATTACKER_QUERY_TOKEN" }// 3) Capture outbound request on a local listenerconstserver=http.createServer((req,res)=>{console.log('=== captured outbound request ===');console.log(JSON.stringify({method: req.method,url: req.url,authorization: req.headers.authorization||null,},null,2));res.end('{}');server.close();scrub();});server.listen(0,'127.0.0.1',()=>{constport=server.address().port;// 4) Realistic application wrapper: optional per-call overrides.// `auth: opts.auth || {}` is the common pattern — empty own object,// but inherited values walk the prototype chain.functionmakeRequest(targetUrl,opts={}){returnaxios.get(targetUrl,{timeout: 5000,auth: opts.auth||{},params: opts.params||{},});}makeRequest(`http://127.0.0.1:${port}/api/widget`).catch((e)=>{console.error('axios error:',e.message);scrub();process.exit(1);});});
dmljdGltLXVzZXI6dmljdGltLXBhc3N3b3JkLWxlYWtlZA== base64-decodes to victim-user:victim-password-leaked. The querystring carries ?leak=ATTACKER_QUERY_TOKEN, which can be a full data-exfil channel
in real chains (CSRF token, session cookie via req.headers, etc.).
Impact
Credential exfiltration via Basic auth header on the outbound
request. If the request URL is attacker-influenced too (common in
webhook/oauth-callback patterns), the credentials flow directly to
the attacker. If not, they flow to the legitimate destination but
expose victim credentials in any logs / proxies along the path.
Outbound request-shape control via inherited params / paramsSerializer. With paramsSerializer polluted to an attacker
function, axios will execute that function with each params
invocation — same-process code execution from a pollution primitive.
Amplifier framing is still correct. The application-side
precondition is "deep-merges attacker JSON into a config object
without __proto__/constructor filtering, then uses the empty-
fallback wrapper auth: opts.auth || {} / params: opts.params || {}."
Both halves are very common in real codebases (we tested defaults-deep, hand-rolled merges, and several lodash-family
utilities; many still pollute).
The params / paramsSerializer half is already handled by open
PR #10922's own('params') / own('paramsSerializer') change — that
PR should be rebased / merged.
Relationship to recent prototype-pollution work
Same vulnerability class as the existing public hardening, just at
sub-field granularity:
This report adds: regular-request auth.username / auth.password
sub-field reads in both the http adapter (lines 737–740) and
resolveConfig.js (line 68).
I'm happy to submit the patch as a PR if that helps. Or, if you'd
prefer to fold this into open PR #10922 (whose author is actively
responding to comments), please let me know and I'll coordinate.
Threat model honesty: this is amplifier framing — exploitation
requires a separate prototype-pollution primitive elsewhere in the
host process. That's how the existing GHSA-q8qp-cvcw-x6jj and
PR #10833 were framed too, so the precedent for "in-scope as a
hardening fix" is established.
Axios versions containing lib/helpers/shouldBypassProxy.js do not treat 0.0.0.0 as a local address when evaluating NO_PROXY rules. In Node.js applications that use HTTP_PROXY or HTTPS_PROXY together with NO_PROXY=localhost,127.0.0.1,::1 or similar, a request to http://0.0.0.0:<port>/ can be routed through the configured proxy instead of bypassing it.
The issue is exploitable when an attacker can influence the axios request URL or a followed redirect target, and when the proxy can reach or relay 0.0.0.0 to local services. This is a Node.js runtime proxy-routing issue, not a browser, install-time, or development-tooling issue.
Impact
Applications are affected when all of the following are true:
The application runs axios in Node.js with the HTTP adapter.
The process uses environment proxy variables such as HTTP_PROXY or HTTPS_PROXY.
The process uses NO_PROXY entries such as localhost, 127.0.0.1, or ::1 to keep local traffic out of the proxy path.
Attacker-controlled input can influence the request URL or redirect target.
The configured proxy does not reject 0.0.0.0 and can reach the local destination.
For plain HTTP targets, the proxy can receive the full request URL, headers, and body, and may be able to observe the local service response. HTTPS targets are less exposed because axios uses CONNECT tunneling in current versions.
Affected Functionality
Affected functionality is limited to environment-derived proxy selection in the Node HTTP adapter:
lib/adapters/http.js calls getProxyForUrl(location) and then shouldBypassProxy(location) before applying the proxy.
lib/helpers/shouldBypassProxy.js normalizes and compares NO_PROXY entries.
Browser, React Native, XHR, and fetch adapter behavior are not affected.
Technical Details
lib/helpers/shouldBypassProxy.js defines local loopback equivalence through isLoopback(). The current implementation recognizes localhost, IPv4 127.0.0.0/8, IPv6 ::1, and IPv4-mapped loopback forms, but it does not include 0.0.0.0.
At lib/helpers/shouldBypassProxy.js:176, axios treats two hosts as matching when both are considered loopback:
Because isLoopback('0.0.0.0') returns false, NO_PROXY=localhost,127.0.0.1,::1 does not match http://0.0.0.0:<port>/. lib/adapters/http.js:185-193 then applies the environment proxy.
Expected safe behavior: both 127.0.0.1 and 0.0.0.0 bypass the proxy when the NO_PROXY policy is intended to cover local destinations.
Observed behavior: 127.0.0.1 bypasses the proxy, while 0.0.0.0 is sent through the proxy.
Workarounds
Add 0.0.0.0 explicitly to NO_PROXY where local addresses must bypass proxies.
Reject or normalize 0.0.0.0 in application URL validation before calling axios.
Set proxy: false on axios requests that must never use environment proxies.
Configure the proxy itself to reject 0.0.0.0, loopback, link-local, and internal address ranges.
Original Report
Summary
axios versions 1.15.0–1.16.1 contain an incomplete loopback-address check in lib/helpers/shouldBypassProxy.js. The isLoopback() function correctly identifies 127.0.0.0/8 and ::1 as loopback addresses but does not recognise 0.0.0.0 — the IPv4 unspecified address, which routes to the local machine on Linux and macOS.
An attacker who controls a URL passed to axios can use http://0.0.0.0/<path> to bypass proxy-based SSRF filtering that the application relies upon.
Details
Affected versions
>= 1.15.0, <= 1.16.1
The vulnerability was introduced in v1.15.0 when the shouldBypassProxy helper was added as a security improvement (PR #10661).
Root cause
File:lib/helpers/shouldBypassProxy.js
// Line 1 — static allowlist (incomplete)constLOOPBACK_HOSTNAMES=newSet(['localhost']);// ← 0.0.0.0 missingconstisIPv4Loopback=(host)=>{constparts=host.split('.');if(parts.length!==4)returnfalse;if(parts[0]!=='127')returnfalse;// ← 0.0.0.0: parts[0] = '0' → falsereturnparts.every((p)=>/^\d+$/.test(p)&&Number(p)>=0&&Number(p)<=255);};constisLoopback=(host)=>{if(!host)returnfalse;if(LOOPBACK_HOSTNAMES.has(host))returntrue;// ← '0.0.0.0' not in setif(isIPv4Loopback(host))returntrue;// ← returns false for 0.0.0.0returnisIPv6Loopback(host);};isLoopback('0.0.0.0')returnsfalse.Node's WHATWG URL parser does not normalise 0.0.0.0 to 127.0.0.1. Other bypass forms are safe: new URL('http://0177.0.0.1/').hostname → '127.0.0.1' (octal), new URL('http://2130706433/').hostname → '127.0.0.1' (decimal), new URL('http://0x7f000001/').hostname → '127.0.0.1' (hex). Only 0.0.0.0 escapes normalisation.
##### PoC'use strict';// Verbatim copy of relevant logic from axios v1.16.1 shouldBypassProxy.jsconstLOOPBACK_HOSTNAMES=newSet(['localhost']);constisIPv4Loopback=(host)=>{constparts=host.split('.');if(parts.length!==4)returnfalse;if(parts[0]!=='127')returnfalse;returnparts.every((p)=>/^\d+$/.test(p)&&Number(p)>=0&&Number(p)<=255);};constisLoopback=(host)=>{if(!host)returnfalse;if(LOOPBACK_HOSTNAMES.has(host))returntrue;returnisIPv4Loopback(host);};// 1. Show URL parser does NOT normalise 0.0.0.0console.log(newURL('http://0.0.0.0/').hostname);// → '0.0.0.0' ← NOT normalisedconsole.log(newURL('http://0177.0.0.1/').hostname);// → '127.0.0.1' ← normalised (safe)console.log(newURL('http://2130706433/').hostname);// → '127.0.0.1' ← normalised (safe)// 2. Show isLoopback fails for 0.0.0.0console.log(isLoopback('0.0.0.0'));// → false ← BUG: should be trueconsole.log(isLoopback('127.0.0.1'));// → true ← correctVerifiedoutputonNode.jsv22/axiosv1.16.1:
0.0.0.0←NOTnormalisedbyURLparser127.0.0.1←octalnormalisedcorrectly127.0.0.1←decimalnormalisedcorrectlyfalse←0.0.0.0notdetectedasloopback⚠true←127.0.0.1correctlydetected
##### Impact
Applications that:
Acceptuser-suppliedURLsandpassthemtoaxiosUseaproxywithNO_PROXY=localhost(orsimilar)forSSRFfiltering…canbebypassedby supplying http://0.0.0.0/<path>. Axios routes the request through the proxy (shouldBypassProxy returns false). If the proxy itself does not filter 0.0.0.0, the connection reaches the local machine — exposing internal services such as cloud IMDS endpoints, internal admin panels, or microservice APIs.FixMinimal(oneline):
-constLOOPBACK_HOSTNAMES=newSet(['localhost']);+constLOOPBACK_HOSTNAMES=newSet(['localhost','0.0.0.0']);
Comprehensive:
constisIPv4Unspecified=(host)=>host==='0.0.0.0';constisLoopback=(host)=>{if(!host)returnfalse;if(LOOPBACK_HOSTNAMES.has(host))returntrue;if(isIPv4Loopback(host))returntrue;if(isIPv4Unspecified(host))returntrue;// add this linereturnisIPv6Loopback(host);};</details>
#### Severity
- CVSS Score: 6.9 / 10 (Medium)
- Vector String: `CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:H/SI:N/SA:N`
#### References
- [https://github.com/axios/axios/security/advisories/GHSA-f4gw-2p7v-4548](https://redirect.github.com/axios/axios/security/advisories/GHSA-f4gw-2p7v-4548)
- [https://github.com/axios/axios/pull/11000](https://redirect.github.com/axios/axios/pull/11000)
- [https://github.com/axios/axios/pull/11001](https://redirect.github.com/axios/axios/pull/11001)
- [https://github.com/axios/axios/commit/1417285c69344bbcc6420a021f67dee0c6fedb2d](https://redirect.github.com/axios/axios/commit/1417285c69344bbcc6420a021f67dee0c6fedb2d)
- [https://github.com/axios/axios/commit/32fc489632377d214db55bfa4e2c48486a7d7ce2](https://redirect.github.com/axios/axios/commit/32fc489632377d214db55bfa4e2c48486a7d7ce2)
- [https://github.com/axios/axios/releases/tag/v0.33.0](https://redirect.github.com/axios/axios/releases/tag/v0.33.0)
- [https://github.com/axios/axios/releases/tag/v1.18.0](https://redirect.github.com/axios/axios/releases/tag/v1.18.0)
- [https://github.com/advisories/GHSA-f4gw-2p7v-4548](https://redirect.github.com/advisories/GHSA-f4gw-2p7v-4548)
This data is provided by the [GitHub Advisory Database](https://redirect.github.com/advisories/GHSA-f4gw-2p7v-4548) ([CC-BY 4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>---
### AxiosNodeHTTPadaptercanuseaninheritedproxyafterinterceptorconfigcloning[GHSA-gcfj-64vw-6mp9](https://redirect.github.com/advisories/GHSA-gcfj-64vw-6mp9)<details><summary>More information</summary>
#### Details
##### SummaryAxios’Node.jsHTTPadaptercanrouterequeststhroughanattacker-controlledproxywhen`Object.prototype.proxy`ispollutedandrequestconfigurationismaterializedasaregularobjectbeforedispatch.Recentaxiosreleaseshardenmergedrequestconfigbycreatinganull-prototypeobject.However,requestinterceptorsrunafterthatmergeandmayreturnareplacementconfig.Acommonimmutableinterceptorpatternsuchas`{...config}`or`Object.assign({}, config)`convertsthehardenedconfigbackintoanormalobject.Axiosthendispatchesthatobjectwithoutre-hardeningit,andtheNodeHTTPadapterreads`config.proxy`throughtheprototypechain.
##### ImpactInaNode.jsdeploymentusingtheHTTPadapter,anattackerwhocantriggerprototypepollutionelsewhereintheprocesscanrouteaffectedHTTPrequeststhroughanattacker-controlledproxy.ThehighestconfirmedimpactisforplaintextHTTPrequests.Theproxycanobserveexplicit`Authorization`headers,axios-generatedBasicauthfrom`config.auth`,requestmethod,absoluteURL,`Host`,andrequestbodycontent.Theproxycanalsoreturnitsownresponsetoaxiosfortheaffectedrequest.Thisdoesnotestablishbrowserimpact.ItalsodoesnotestablishHTTPSheaderorbodydisclosureundernormalTLSvalidation.
##### AffectedFunctionalityAffectedfunctionalityislimitedtoaxiosrequeststhatusetheNode.jsHTTPadapter,includingdefaultNodeusagewhentheHTTPadapterisselectedandexplicit`adapter: 'http'`usage.Therelevantconfigurationpathis`config.proxy`intheNodeHTTPadapter.Thehardened-bypasspathrequiresarequestinterceptorsuch as:
```jsapi.interceptors.request.use((config)=>({
...config,headers: {
...config.headers,'X-App': 'demo'}}));
Unaffected or mitigating conditions include browser adapters, the Node fetch adapter, no polluted Object.prototype.proxy, an own proxy: false or safe own proxy value on the config, and hardened releases where interceptors return the original null-prototype config instead of a regular object clone.
Technical Details
lib/core/mergeConfig.js creates a null-prototype merged config and uses own-property reads for merged values. This is intended to prevent polluted Object.prototype values from affecting config behavior.
lib/core/Axios.js runs request interceptors after the merge. In both the asynchronous and synchronous interceptor paths, axios passes the interceptor-returned config into dispatch.
lib/core/dispatchRequest.js accepts that returned config, transforms request data, selects the adapter, and calls the adapter without re-hardening or re-normalizing the config.
lib/adapters/http.js uses own-property reads for several sensitive fields, but the initial proxy dispatch path still passes config.proxy directly into setProxy(). If an interceptor returned a regular object, config.proxy can resolve to inherited Object.prototype.proxy.
Expected vulnerable result: the response comes from the proxy, targetHits is empty, and proxyHits contains the absolute URL, authorization header, host header, and request body.
Workarounds
Set an own proxy: false on affected requests or on an axios instance when proxy support is not required.
Avoid request interceptors that return regular object clones of config in hardened releases. Returning the original config or cloning into a null-prototype object avoids this specific bypass, but this is fragile and should not replace a fix.
Use the Node fetch adapter for affected requests where its behavior is compatible with the application.
Original Report
Summary
Axios hardens merged request config by creating a null-prototype object, preventing polluted Object.prototype properties from influencing request behavior. Request interceptors run after that hardening, and a normal immutable
interceptor pattern such as {...config} or Object.assign({}, config) re-materializes the config as a regular object. Axios then dispatches that interceptor-returned object without re-hardening it. In the Node HTTP adapter, config.proxy
is read through the prototype chain, allowing a polluted Object.prototype.proxy to route authenticated HTTP requests through an attacker-controlled proxy.
Impact
In a Node.js deployment using the HTTP adapter, an attacker who can trigger prototype pollution elsewhere in the process can cause affected axios requests to be sent through an attacker-controlled proxy when the application uses a
request interceptor that returns a plain object copy of the config.
Verified local impact:
Authenticated request redirection to attacker-controlled proxy.
Disclosure of explicit Authorization headers.
Disclosure of axios-generated Basic auth headers from config.auth.
Disclosure of request metadata: method, absolute URL, Host header.
Disclosure of POST body content.
This report does not claim browser impact or proven HTTPS credential disclosure. The demonstrated credential and body disclosure is for Node HTTP-adapter requests over HTTP/plaintext.
Affected component
The affected component is the Node.js HTTP adapter request path after request interceptors have run.
The issue requires:
Node.js HTTP adapter usage.
A polluted Object.prototype.proxy.
A request interceptor that returns a plain object copy of the config.
No own proxy: false or safe own proxy property on the request config.
Affected versions
Confirmed affected for this specific hardening-bypass variant:
axios@1.15.2
axios@1.16.0
axios@1.16.0 was the latest published version observed via npm view axios version during validation.
Related older behavior observed during testing:
1.13.0, 1.13.6, 1.14.0, 1.15.0, and 1.15.1 routed via inherited Object.prototype.proxy even without the interceptor re-materialization step. That is related background, not the narrowed hardening-bypass variant described here.
The Node HTTP adapter later consults config.proxy, and this read is reachable through the prototype chain once the interceptor has re-materialized the config as a normal object. As a result, a polluted Object.prototype.proxy can
redirect the outgoing authenticated request through an attacker-controlled proxy.
Permalink: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/adapters/http.js#L816-L820
Why this is a security issue and not intended behavior
Axios’ threat model explicitly treats polluted Object.prototype config reads as high-impact read-side gadgets and states that axios defends reachable config-read gadgets through own-property checks and null-prototype structures. The
existing regression tests also assert that a polluted Object.prototype.proxy must not route requests through an attacker proxy.
This behavior is therefore a bypass of axios’ existing prototype-pollution hardening, not merely a generic “polluted process” complaint. The interceptor does not need to be malicious; it can be ordinary application code that returns an
immutable copy of the config. The attacker-controlled piece is the polluted prototype property supplied by a separate vulnerability or dependency.
Realistic threat model
A realistic exploit chain is:
A transitive dependency or upstream parser bug allows prototype pollution in a Node.js process.
The polluted property is Object.prototype.proxy, with host and port pointing to an attacker-controlled proxy.
The application uses axios with a request interceptor that returns a plain object copy, such as adding headers immutably.
The application sends an HTTP request with credentials or sensitive body data.
Axios routes that request through the inherited proxy configuration.
This requires a prototype pollution primitive and a compatible interceptor pattern. It does not require the attacker to control the interceptor.
Fetch adapter in Node with the same interceptor: proxy receives none.
Suggested remediation
Re-harden the final request config after all request interceptors and before adapter dispatch. This should cover both asynchronous and synchronous interceptor paths.
A practical fix would be to normalize the interceptor-returned object into a null-prototype, own-property-only config before calling dispatchRequest(), or at the start of dispatchRequest() itself. Security-sensitive adapter reads should
also consistently use own-property access helpers. In particular, the Node HTTP adapter should not read config.proxy through the prototype chain.
Minimal regression test
Add an end-to-end Node HTTP adapter test that:
Starts a target server and attacker proxy on 127.0.0.1.
Sets Object.prototype.proxy to the attacker proxy.
Adds a request interceptor returning {...config, headers: {...config.headers}}.
Sends a request with an Authorization header.
Asserts the target server receives the request.
Asserts the attacker proxy receives no request.
Asserts the final config no longer exposes inherited proxy.
A second assertion can cover config.auth to ensure axios-generated Basic auth is not sent to the attacker proxy.
Axios can consume inherited properties from nested request option objects when the JavaScript process already has a polluted Object.prototype.
The top-level merged config is protected with a null prototype, but nested plain objects such as auth and paramsSerializer are cloned into ordinary objects. If application code passes placeholders such as auth: {} or paramsSerializer: {}, inherited username, password, encode, or serialize properties can influence outbound requests.
Impact
This is reachable only when another component has already polluted Object.prototype and the application passes an affected nested axios option object.
Confirmed impacts include silent injection of an Authorization: Basic ... header from inherited username and password values, and query-string tampering when inherited paramsSerializer fields are function-valued.
The auth case requires only string-valued pollution. Full query-string replacement through paramsSerializer.serialize requires a function-valued pollution primitive; string-only pollution may still cause request failures or encoding changes through encode.
This does not mean every axios request is affected. Requests that do not pass auth, do not pass paramsSerializer, or provide explicit own properties for the relevant nested fields are not affected by this specific gadget.
Affected Functionality
Affected runtime functionality:
Node HTTP adapter Basic auth handling in lib/adapters/http.js.
Browser/fetch/XHR Basic auth handling through lib/helpers/resolveConfig.js.
Query serialization through lib/helpers/buildURL.js.
axios.getUri() when called with an affected paramsSerializer object.
Affected config shapes:
auth: {} or an auth object missing own username and/or password.
paramsSerializer: {} or a paramsSerializer object missing own encode and/or serialize.
Unaffected by this specific issue:
Requests with no auth property.
Requests with no paramsSerializer property.
Top-level polluted auth or paramsSerializer values in current hardened versions.
Technical Details
lib/core/mergeConfig.js creates the top-level merged config with Object.create(null), but nested object cloning still uses ordinary {} containers:
If upgrading is not yet possible, avoid passing placeholder nested option objects.
Remove auth entirely when Basic auth is not intended. For paramsSerializer objects, provide explicit own encode and serialize properties or remove paramsSerializer when custom serialization is not required.
These workarounds only address this axios gadget. They do not remediate the separate prototype-pollution primitive that must already exist in the application process.
Original Report
Summary
axios 1.16.1 mitigates prototype-pollution gadgets on the top-level request config but not on nested option objects. When a caller passes a partial nested option object such as auth: {} or paramsSerializer: {}, axios reads inner fields (username, password, encode, serialize) through the prototype chain. If Object.prototype has been polluted by another component in the same Node.js process, those inherited values are silently injected into the outbound request, including the Authorization header and the serialized query string.
Details
mergeConfig (lib/core/mergeConfig.js) was hardened to use a null-prototype container for the top-level config, but its nested-clone helper still produces ordinary {} containers:
mergeConfig.js Lines 36-45
function getMergedValue(target, source, prop, caseless) {
if (utils.isPlainObject(target) && utils.isPlainObject(source)) {
return utils.merge.call({ caseless }, target, source);
} else if (utils.isPlainObject(source)) {
return utils.merge({}, source);
} else if (utils.isArray(source)) {
return source.slice();
}
return source;
}
The cloned nested objects therefore inherit from Object.prototype. Downstream consumers read sensitive fields via plain dotted access, with no own-property guard:
Because auth.username, auth.password, options.encode, and options.serialize are accessed without hasOwnProperty checks, a polluted Object.prototype.username / Object.prototype.password / Object.prototype.serialize flows directly into the outgoing request.
The auth sink is the primary impact (silent Basic-auth injection); paramsSerializer.serialize is a secondary but powerful sink because it can fully replace the query string.
PoC
import http from 'node:http';
import axios from '../../index.js';
const ATTACKER_USER = 'attacker';
const ATTACKER_PASS = 'exfil';
const ATTACKER_BASIC = Buffer.from(`${ATTACKER_USER}:${ATTACKER_PASS}`).toString('base64');
// Step 1: simulate a pre-existing prototype-pollution primitive in this process.
// In reality, a separate dependency would have done this. We keep the
// "polluted" properties non-enumerable so they only affect inherited reads,
// which is the realistic shape of most prototype-pollution gadgets.
Object.defineProperty(Object.prototype, 'username', {
value: ATTACKER_USER,
configurable: true,
});
Object.defineProperty(Object.prototype, 'password', {
value: ATTACKER_PASS,
configurable: true,
});
Object.defineProperty(Object.prototype, 'serialize', {
value: () => 'polluted=1',
configurable: true,
});
// Local capture server.
const server = http.createServer((req, res) => {
const captured = {
authorization: req.headers['authorization'] || null,
url: req.url,
};
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify(captured));
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const port = server.address().port;
try {
// Application code: passes nested *placeholder* option objects that have
// no own auth/serializer properties. Without prototype pollution this is
// a no-op. With prototype pollution it becomes attacker-controlled state.
const response = await axios.get(`http://127.0.0.1:${port}/demo`, {
auth: {},
paramsSerializer: {},
params: { unused: 'ignored-by-polluted-serializer' },
});
console.log('--- PoC: nested-option prototype-pollution gadgets ---');
console.log('Server saw:', JSON.stringify(response.data));
const authLeaked = response.data.authorization === `Basic ${ATTACKER_BASIC}`;
const urlRewritten = response.data.url === '/demo?polluted=1';
if (authLeaked && urlRewritten) {
cons
> ✂ **Note**
>
> PR body was truncated to here.
Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.
This PR includes no changesets
When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types
renovateBot
changed the title
chore(deps): update dependency axios to v1.16.0 [security]
chore(deps): update dependency axios to ^1.11.0 [security]
Jun 5, 2026
renovateBot
changed the title
chore(deps): update dependency axios [security]
chore(deps): update dependency axios to v1.18.0 [security]
Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
1.17.0→1.18.0^1.17.0→^1.18.0Warning
Some dependencies could not be looked up. Check the Dependency Dashboard for more information.
Axios: Prototype pollution auth subfields can inject Basic auth
GHSA-xj6q-8x83-jv6g
More information
Details
Summary
Axios versions after the
GHSA-q8qp-cvcw-x6jjfix 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 ownauthobject that omitsusernameorpassword, axios reads inheritedObject.prototype.usernameandObject.prototype.passwordvalues and uses them to construct an outboundAuthorization: Basic ...header.This 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 || {}.Impact
An attacker who can pollute
Object.prototype.usernameand/orObject.prototype.passwordcan influence the Basic auth header on affected axios requests that pass an empty or partial ownauthobject.The practical impact is outbound request tampering. The attacker can inject attacker-chosen Basic auth credentials, replace an existing
Authorizationheader because axios removes it whenauthis used, or cause downstream authorization failures.This 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.
Affected Functionality
Affected functionality:
lib/adapters/http.js.lib/helpers/resolveConfig.js.config.authis an own object butusernameand/orpasswordare absent own properties.Unaffected or not accepted as core impact:
authobject aftermergeConfig().auth.usernameandauth.passwordvalues.params/paramsSerializerafter the null-prototypemergeConfig()hardening.paramsSerializerfunctions 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.Technical Details
mergeConfig()returns a null-prototype top-level config object, which prevents top-level reads such asconfig.authfrom inheriting polluted values. However, nested plain objects returned byutils.merge()still haveObject.prototype.In
lib/adapters/http.js, axios correctly reads the top-levelauthvalue throughown('auth'), but then reads subfields directly:If the caller passes auth: {} and Object.prototype.username/password are polluted, those direct subfield reads walk the prototype chain.
The same pattern exists in
lib/helpers/resolveConfig.js:The fix should guard
usernameandpasswordwithutils.hasOwnProp, matching the proxy-auth pattern already used elsewhere.Proof of Concept of Attack
Safe local PoC against published
axios@1.16.1:Expected output:
{ "url": "/api", "authorization": "Basic dmljdGltLXVzZXI6dmljdGltLXBhc3N3b3JkLWxlYWtlZA==" }The base64 value decodes to
victim-user:victim-password-leaked.Workarounds
Avoid passing empty or partial
authobjects. Only setauthwhen the application has own username and password values.Applications that merge untrusted input should filter
__proto__,constructor, andprototype, and should read optional user options with own-property checks rather thanopts.auth || {}.Where a wrapper must materialize optional auth, use a null-prototype object or explicitly copy only own fields.
Original Report
Summary
After GHSA-q8qp-cvcw-x6jj / PR #10779 (shipped in
v1.15.2) and the further proxy-side hardening inPR #10833 (merged 2026-05-02), the top-level
config.authand the proxy authsub-fields are correctly read viautils.hasOwnProp. The regular request auth sub-fields (config.auth.usernameandconfig.auth.password) and theconfig.params/config.paramsSerializerreads insideresolveConfig.jsare still unguarded against a pollutedObject.prototype.When a polluted host process makes an axios call with the common "optional override" pattern (
auth: opts.auth || {}— an empty own{}), the sub-field readsconfigAuth.usernameandconfigAuth.passwordwalk the prototype chain and return the attacker-controlled values. Same forparamsandparamsSerializer. The outbound HTTP request then carries an attacker-chosenAuthorization: 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).Reproduces against
axiosmainHEAD (34723be, dated 2026-05-24)as well as the released
v1.16.1.Details
Three still-unguarded read sites on
mainHEAD:(1)
lib/adapters/http.jslines 737–740 (Node http adapter):own('auth')correctly applieshasOwnPropto the top-levelauthkey. But once
configAuthis the empty object the caller passed(
auth: {}),configAuth.usernamewalks the prototype chain andpicks up
Object.prototype.username.Contrast with the proxy-auth path that PR #10833 fixed (lines 322–324):
This is the exact pattern needed at lines 739–740 too.
(2)
lib/helpers/resolveConfig.jslines 50 + 68 (xhr/fetch adapter shared resolver):Same shape — top-level guarded, sub-fields walk prototype.
(3)
lib/helpers/resolveConfig.jslines 58–59 (params + paramsSerializer):This third site is already proposed for fix in open PR #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.PoC
This PoC contains zero direct
Object.prototype.x = ywrites. Thepollution flows entirely from attacker-shaped JSON through a real
deep-merge utility (
defaults-deep@0.2.4, ~50k weekly downloads,still walks
constructor.prototype). A hand-rolled deep merge —the canonical insecure backend pattern — exhibits the same pollution
via
__proto__and is more common in real codebases than any namedutility.
Reproduction:
Captured output (verified against released
1.16.1AND againstmainat34723be, 2026-05-24):{ "method": "GET", "url": "/api/widget?leak=ATTACKER_QUERY_TOKEN", "authorization": "Basic dmljdGltLXVzZXI6dmljdGltLXBhc3N3b3JkLWxlYWtlZA==" }dmljdGltLXVzZXI6dmljdGltLXBhc3N3b3JkLWxlYWtlZA==base64-decodes tovictim-user:victim-password-leaked. The querystring carries?leak=ATTACKER_QUERY_TOKEN, which can be a full data-exfil channelin real chains (CSRF token, session cookie via
req.headers, etc.).Impact
request. If the request URL is attacker-influenced too (common in
webhook/oauth-callback patterns), the credentials flow directly to
the attacker. If not, they flow to the legitimate destination but
expose victim credentials in any logs / proxies along the path.
params/paramsSerializer. WithparamsSerializerpolluted to an attackerfunction, axios will execute that function with each
paramsinvocation — same-process code execution from a pollution primitive.
precondition is "deep-merges attacker JSON into a config object
without
__proto__/constructorfiltering, then uses the empty-fallback wrapper
auth: opts.auth || {}/params: opts.params || {}."Both halves are very common in real codebases (we tested
defaults-deep, hand-rolled merges, and several lodash-familyutilities; many still pollute).
Attributes — amplifier sink).
Proposed fix
Two-line change in
http.js, matching the proxy-auth pattern PR#10833 already established:
Same pattern in
resolveConfig.js:The
params/paramsSerializerhalf is already handled by openPR #10922's
own('params')/own('paramsSerializer')change — thatPR should be rebased / merged.
Relationship to recent prototype-pollution work
Same vulnerability class as the existing public hardening, just at
sub-field granularity:
mergeConfigdirect-key reads. Fixed in v1.15.2.mergeDirectKeysin→hasOwnProp. Fixed in v1.15.x.auth.username/passwordsub-fields. Fixed post-1.16.1.formDataToJSONdefense-in-depth. Fixed post-1.16.1.socketPathguard. Merged 2026-05-24.params/paramsSerializerown()guard. Proposed; not merged.This report adds: regular-request
auth.username/auth.passwordsub-field reads in both the http adapter (lines 737–740) and
resolveConfig.js (line 68).
Reporter notes
findings. The bundle's public tracking entry (without the working
exploit chain) is at
georgian-io/package-runtime-security-findings/advisories/AXIOS-002-prototype-pollution-config-fields.md.prefer to fold this into open PR #10922 (whose author is actively
responding to comments), please let me know and I'll coordinate.
requires a separate prototype-pollution primitive elsewhere in the
host process. That's how the existing GHSA-q8qp-cvcw-x6jj and
PR #10833 were framed too, so the precedent for "in-scope as a
hardening fix" is established.
Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Axios: NO_PROXY bypass for 0.0.0.0 local addresses in axios
GHSA-f4gw-2p7v-4548
More information
Details
Summary
Axios versions containing
lib/helpers/shouldBypassProxy.jsdo not treat0.0.0.0as a local address when evaluatingNO_PROXYrules. In Node.js applications that useHTTP_PROXYorHTTPS_PROXYtogether withNO_PROXY=localhost,127.0.0.1,::1or similar, a request tohttp://0.0.0.0:<port>/can be routed through the configured proxy instead of bypassing it.The issue is exploitable when an attacker can influence the axios request URL or a followed redirect target, and when the proxy can reach or relay
0.0.0.0to local services. This is a Node.js runtime proxy-routing issue, not a browser, install-time, or development-tooling issue.Impact
Applications are affected when all of the following are true:
HTTP_PROXYorHTTPS_PROXY.NO_PROXYentries such aslocalhost,127.0.0.1, or::1to keep local traffic out of the proxy path.0.0.0.0and can reach the local destination.For plain HTTP targets, the proxy can receive the full request URL, headers, and body, and may be able to observe the local service response. HTTPS targets are less exposed because axios uses CONNECT tunneling in current versions.
Affected Functionality
Affected functionality is limited to environment-derived proxy selection in the Node HTTP adapter:
lib/adapters/http.jscallsgetProxyForUrl(location)and thenshouldBypassProxy(location)before applying the proxy.lib/helpers/shouldBypassProxy.jsnormalizes and comparesNO_PROXYentries.config.proxyremains trusted caller configuration.Technical Details
lib/helpers/shouldBypassProxy.jsdefines local loopback equivalence throughisLoopback(). The current implementation recognizeslocalhost, IPv4127.0.0.0/8, IPv6::1, and IPv4-mapped loopback forms, but it does not include0.0.0.0.At
lib/helpers/shouldBypassProxy.js:176, axios treats two hosts as matching when both are considered loopback:Because
isLoopback('0.0.0.0')returnsfalse,NO_PROXY=localhost,127.0.0.1,::1does not matchhttp://0.0.0.0:<port>/.lib/adapters/http.js:185-193then applies the environment proxy.Proof of Concept of Attack
Expected safe behavior: both
127.0.0.1and0.0.0.0bypass the proxy when theNO_PROXYpolicy is intended to cover local destinations.Observed behavior:
127.0.0.1bypasses the proxy, while0.0.0.0is sent through the proxy.Workarounds
0.0.0.0explicitly toNO_PROXYwhere local addresses must bypass proxies.0.0.0.0in application URL validation before calling axios.proxy: falseon axios requests that must never use environment proxies.0.0.0.0, loopback, link-local, and internal address ranges.Original Report
Summary
axiosversions 1.15.0–1.16.1 contain an incomplete loopback-address check inlib/helpers/shouldBypassProxy.js. TheisLoopback()function correctly identifies127.0.0.0/8and::1as loopback addresses but does not recognise0.0.0.0— the IPv4 unspecified address, which routes to the local machine on Linux and macOS.An attacker who controls a URL passed to axios can use
http://0.0.0.0/<path>to bypass proxy-based SSRF filtering that the application relies upon.Details
Affected versions
>= 1.15.0, <= 1.16.1The vulnerability was introduced in v1.15.0 when the
shouldBypassProxyhelper was added as a security improvement (PR #10661).Root cause
File:
lib/helpers/shouldBypassProxy.jsUnaffected or mitigating conditions include browser adapters, the Node fetch adapter, no polluted
Object.prototype.proxy, an ownproxy: falseor safe ownproxyvalue on the config, and hardened releases where interceptors return the original null-prototype config instead of a regular object clone.Technical Details
lib/core/mergeConfig.jscreates a null-prototype merged config and uses own-property reads for merged values. This is intended to prevent pollutedObject.prototypevalues from affecting config behavior.lib/core/Axios.jsruns request interceptors after the merge. In both the asynchronous and synchronous interceptor paths, axios passes the interceptor-returned config into dispatch.lib/core/dispatchRequest.jsaccepts that returned config, transforms request data, selects the adapter, and calls the adapter without re-hardening or re-normalizing the config.lib/adapters/http.jsuses own-property reads for several sensitive fields, but the initial proxy dispatch path still passesconfig.proxydirectly intosetProxy(). If an interceptor returned a regular object,config.proxycan resolve to inheritedObject.prototype.proxy.Proof of Concept of Attack
Expected vulnerable result: the response comes from the proxy,
targetHitsis empty, andproxyHitscontains the absolute URL, authorization header, host header, and request body.Workarounds
Set an own
proxy: falseon affected requests or on an axios instance when proxy support is not required.Avoid request interceptors that return regular object clones of config in hardened releases. Returning the original config or cloning into a null-prototype object avoids this specific bypass, but this is fragile and should not replace a fix.
Use the Node fetch adapter for affected requests where its behavior is compatible with the application.
Original Report
Summary
Axios hardens merged request config by creating a null-prototype object, preventing polluted Object.prototype properties from influencing request behavior. Request interceptors run after that hardening, and a normal immutable
interceptor pattern such as {...config} or Object.assign({}, config) re-materializes the config as a regular object. Axios then dispatches that interceptor-returned object without re-hardening it. In the Node HTTP adapter, config.proxy
is read through the prototype chain, allowing a polluted Object.prototype.proxy to route authenticated HTTP requests through an attacker-controlled proxy.
Impact
In a Node.js deployment using the HTTP adapter, an attacker who can trigger prototype pollution elsewhere in the process can cause affected axios requests to be sent through an attacker-controlled proxy when the application uses a
request interceptor that returns a plain object copy of the config.
Verified local impact:
This report does not claim browser impact or proven HTTPS credential disclosure. The demonstrated credential and body disclosure is for Node HTTP-adapter requests over HTTP/plaintext.
Affected component
The affected component is the Node.js HTTP adapter request path after request interceptors have run.
The issue requires:
Affected versions
Confirmed affected for this specific hardening-bypass variant:
axios@1.16.0 was the latest published version observed via npm view axios version during validation.
Related older behavior observed during testing:
Root cause
Initial hardening
Axios initially hardens merged request config by creating a null-prototype object in mergeConfig(), which is meant to prevent inherited Object.prototype properties from influencing request behavior.
Permalink: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/core/mergeConfig.js#L21-L25
Interceptor re-materialization
Request interceptors run after that hardening step, and axios allows an interceptor to return a replacement config object. A common immutable pattern such as {...config} or Object.assign({}, config) converts the hardened null-
prototype config back into a normal object with Object.prototype as its prototype.
Permalinks: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/core/Axios.js#L187-L199, https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/core/Axios.js#L204-L218
No post-interceptor re-hardening
Axios passes the interceptor-returned config into request dispatch without restoring the null-prototype property or otherwise normalizing the object into an own-property-only structure.
Permalink: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/core/dispatchRequest.js#L34-L48
Prototype-chain read of proxy in the Node adapter
The Node HTTP adapter later consults config.proxy, and this read is reachable through the prototype chain once the interceptor has re-materialized the config as a normal object. As a result, a polluted Object.prototype.proxy can
redirect the outgoing authenticated request through an attacker-controlled proxy.
Permalink: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/adapters/http.js#L816-L820
Why this is a security issue and not intended behavior
Axios’ threat model explicitly treats polluted Object.prototype config reads as high-impact read-side gadgets and states that axios defends reachable config-read gadgets through own-property checks and null-prototype structures. The
existing regression tests also assert that a polluted Object.prototype.proxy must not route requests through an attacker proxy.
This behavior is therefore a bypass of axios’ existing prototype-pollution hardening, not merely a generic “polluted process” complaint. The interceptor does not need to be malicious; it can be ordinary application code that returns an
immutable copy of the config. The attacker-controlled piece is the polluted prototype property supplied by a separate vulnerability or dependency.
Realistic threat model
A realistic exploit chain is:
This requires a prototype pollution primitive and a compatible interceptor pattern. It does not require the attacker to control the interceptor.
Proof of concept
Save as poc.mjs in the axios repository root:
Run:
Observed results
Representative observed output from local loopback testing:
That value decodes to:
svc-account:prod-secret
Negative controls were also tested:
Suggested remediation
Re-harden the final request config after all request interceptors and before adapter dispatch. This should cover both asynchronous and synchronous interceptor paths.
A practical fix would be to normalize the interceptor-returned object into a null-prototype, own-property-only config before calling dispatchRequest(), or at the start of dispatchRequest() itself. Security-sensitive adapter reads should
also consistently use own-property access helpers. In particular, the Node HTTP adapter should not read config.proxy through the prototype chain.
Minimal regression test
Add an end-to-end Node HTTP adapter test that:
A second assertion can cover config.auth to ensure axios-generated Basic auth is not sent to the attacker proxy.
References / permalinks
Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Axios: Nested axios option objects can consume polluted prototype values
GHSA-7q8q-rj6j-mhjq
More information
Details
Summary
Axios can consume inherited properties from nested request option objects when the JavaScript process already has a polluted
Object.prototype.The top-level merged config is protected with a null prototype, but nested plain objects such as
authandparamsSerializerare cloned into ordinary objects. If application code passes placeholders such asauth: {}orparamsSerializer: {}, inheritedusername,password,encode, orserializeproperties can influence outbound requests.Impact
This is reachable only when another component has already polluted
Object.prototypeand the application passes an affected nested axios option object.Confirmed impacts include silent injection of an
Authorization: Basic ...header from inheritedusernameandpasswordvalues, and query-string tampering when inheritedparamsSerializerfields are function-valued.The
authcase requires only string-valued pollution. Full query-string replacement throughparamsSerializer.serializerequires a function-valued pollution primitive; string-only pollution may still cause request failures or encoding changes throughencode.This does not mean every axios request is affected. Requests that do not pass
auth, do not passparamsSerializer, or provide explicit own properties for the relevant nested fields are not affected by this specific gadget.Affected Functionality
Affected runtime functionality:
lib/adapters/http.js.lib/helpers/resolveConfig.js.lib/helpers/buildURL.js.axios.getUri()when called with an affectedparamsSerializerobject.Affected config shapes:
auth: {}or anauthobject missing ownusernameand/orpassword.paramsSerializer: {}or aparamsSerializerobject missing ownencodeand/orserialize.Unaffected by this specific issue:
authproperty.paramsSerializerproperty.authorparamsSerializervalues in current hardened versions.Technical Details
lib/core/mergeConfig.jscreates the top-level merged config withObject.create(null), but nested object cloning still uses ordinary{}containers:Downstream code then reads nested fields without own-property checks.
In
lib/helpers/resolveConfig.js:In
lib/adapters/http.js:In
lib/helpers/buildURL.js:Proof of Concept of Attack
Observed result:
{ "authorization": "Basic YXR0YWNrZXI6ZXhmaWw=", "url": "/demo?polluted=1" }Workarounds
If upgrading is not yet possible, avoid passing placeholder nested option objects.
Remove
authentirely when Basic auth is not intended. ForparamsSerializerobjects, provide explicit ownencodeandserializeproperties or removeparamsSerializerwhen custom serialization is not required.These workarounds only address this axios gadget. They do not remediate the separate prototype-pollution primitive that must already exist in the application process.
Original Report
Summary
axios 1.16.1 mitigates prototype-pollution gadgets on the top-level request config but not on nested option objects. When a caller passes a partial nested option object such as auth: {} or paramsSerializer: {}, axios reads inner fields (username, password, encode, serialize) through the prototype chain. If Object.prototype has been polluted by another component in the same Node.js process, those inherited values are silently injected into the outbound request, including the Authorization header and the serialized query string.
Details
mergeConfig (lib/core/mergeConfig.js) was hardened to use a null-prototype container for the top-level config, but its nested-clone helper still produces ordinary {} containers:
mergeConfig.js Lines 36-45
The cloned nested objects therefore inherit from Object.prototype. Downstream consumers read sensitive fields via plain dotted access, with no own-property guard:
Browser / fetch Basic auth — lib/helpers/resolveConfig.js:
resolveConfig.js Lines 64-70
Node HTTP adapter Basic auth — lib/adapters/http.js:
http.js Lines 829-836
paramsSerializer reads — lib/helpers/buildURL.js:
buildURL.js Lines 31-54
Because auth.username, auth.password, options.encode, and options.serialize are accessed without hasOwnProperty checks, a polluted Object.prototype.username / Object.prototype.password / Object.prototype.serialize flows directly into the outgoing request.
The auth sink is the primary impact (silent Basic-auth injection); paramsSerializer.serialize is a secondary but powerful sink because it can fully replace the query string.
PoC