chore(deps): update dependency axios to v1.18.0 [security]#2823
Open
renovate[bot] wants to merge 1 commit into
Open
chore(deps): update dependency axios to v1.18.0 [security]#2823renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.16.0→1.18.0Axios: Excessive recursion in formDataToJSON can cause denial of service
GHSA-42h9-826w-cgv3
More information
Details
Summary
Axios versions
0.28.0and later contain uncontrolled recursion informDataToJSON, the helper behind the publicaxios.formToJSON()/ namedformToJSONAPI and the default request transform used when FormData is sent with anapplication/jsoncontent type.Applications are affected when they pass attacker-controlled
FormDatafield names into this functionality. A field name with thousands of nested bracket segments can exhaust the JavaScript call stack and throwRangeError: Maximum call stack size exceeded, causing request failure and, in applications that do not handle the exception or rejected promise, possible process termination.Impact
The impact is denial of service against applications that process untrusted
FormDatafield names through axios' FormData-to-JSON conversion.The vulnerable path is not reached by merely installing axios, by normal multipart
FormDatapass-through, or by ordinary axios requests that do not request JSON serialisation ofFormData. In the default axios request, the error is produced before network I/O and returned as a rejected Promise. Direct use offormToJSON()throws synchronously.Server-side applications are the primary risk when remote users can submit arbitrary form field names, and the application converts those fields with
formToJSON()or sends them through axios as JSON.Affected Functionality
Affected APIs and paths:
axios.formToJSON(formData)import { formToJSON } from "axios"lib/helpers/formDataToJSON.jstransformRequestwhendataisFormDataandContent-Typecontainsapplication/jsonUnaffected or lower-risk paths:
FormDatarequests withoutJSON Content-TypetoFormData()object-to-FormData serialisation, which already has amaxDepthguardTechnical Details
lib/helpers/formDataToJSON.jsparses a form field name into path segments withparsePropPath(). For a key such asa[x][x][x], each bracketed segment becomes another path element.formDataToJSON()then calls the nestedbuildPath(path, value, target, index)function.buildPath()recursively calls itself once for each path segment and does not enforce a maximum depth:const result = buildPath(path, value, target[name], index);A key containing thousands of bracket segments, therefore, creates thousands of recursive calls. At sufficient depth, V8 throws
RangeError: Maximum call stack size exceeded.Axios already applies a depth guard to the inverse serializer in
lib/helpers/toFormData.js, wheremaxDepthdefaults to 100 and exceeding it throwsAxiosErrorwith codeERR_FORM_DATA_DEPTH_EXCEEDED.formDataToJSON()does not currently have equivalent protection.Proof of Concept of Attack
Expected vulnerable result:
RangeError: Maximum call stack size exceeded
The axios request transform path can also be reached before network I/O:
Expected vulnerable result:
RangeError: Maximum call stack size exceeded
Workarounds
Applications can avoid the vulnerable path by not converting attacker-controlled
FormDatato JSON with axios.If conversion is required before a fixed axios release is available, validate
FormDatafield names before callingformToJSON()or before sendingFormDatawithContent-Type: application/json. Reject keys whose parsed nesting depth exceeds the application's expected schema.For axios requests carrying untrusted
FormData, avoid settingContent-Type: application/json; leaving the data as multipart FormData bypassesformDataToJSON().Catching the resulting error can prevent process termination, but it does not remove the uncontrolled-recursion behaviour and should not be treated as the primary mitigation.
Original Report
Axios SSRF via Incomplete Loopback Detection
CWE-918 | CVSS 7.5 (HIGH) | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
1. Classification
2. Description
Summary
The
shouldBypassProxy()function in Axios fails to recognise0.0.0.0,::, and::ffff:0.0.0.0as loopback addresses. WhenNO_PROXY=localhostis configured, requests to these addresses are incorrectly forwarded through the proxy instead of being sent directly, enabling an SSRF attack against internal services reachable via the proxy's loopback interface.Root Cause
File:
lib/helpers/shouldBypassProxy.jsisIPv4Loopback(lines 3-8): Only checks for127.x.x.xaddresses by inspectingparts[0] !== '127'. The0.0.0.0address hasparts[0] === '0', so it falls through as non-loopback, even though on Linux0.0.0.0routes to the loopback interface.isIPv6Loopback(lines 10-38): Only checkshost === '::1'. The::address (unspecified IPv6) also routes to the loopback, but is not recognised.Attack Flow:
Attack Vector
3. Proof of Concept
Phase 1: Logic Verification
Phase 2: Docker E2E Reproduction
A full 3-container Docker reproduction was created and tested:
Reproduction steps:
Results:
127.0.0.1 + NO_PROXY=localhost→ BYPASS (correct)0.0.0.0 + NO_PROXY=localhost→ VIA_PROXY (SSRF)[::] + NO_PROXY=localhost→ VIA_PROXY (SSRF)[::ffff:0.0.0.0] + NO_PROXY=localhost→ VIA_PROXY (SSRF)Phase 3: Actual Axios Client
The real Axios HTTP client (v1.16.1, source tree) was tested through proxy configuration:
proxy: { host: 'proxy', port: 8888 }NO_PROXY=localhostand requestinghttp://0.0.0.0:9999/4. Impact
Attack Scenario
NO_PROXY=localhostto protect internal serviceshttp://0.0.0.0:8080/adminas the target URL0.0.0.0→ the proxy's own loopback → reaches the internal admin service on port 8080Potential Consequences
Likelihood
5. Remediation
Code Fix
File:
lib/helpers/shouldBypassProxy.jsWorkarounds
0.0.0.0and::to theNO_PROXYenvironment variable explicitly127.0.0.1instead of0.0.0.0in all internal service URLs0.0.0.0and::before passing to AxiosSeverity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:NReferences
This data is provided by OSV and 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
Impact
Concrete consequences:
Details
Severity
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:L/SA:NReferences
This data is provided by OSV and 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:
[email protected] 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 OSV and the GitHub Advisory Database (CC-BY 4.0).
Axios form serializer maxDepth bypass via {} metatoken
GHSA-hcpx-6fm6-wx23
More information
Details
Summary
Axios versions in the fixed lines for GHSA-62hf-57xw-28j9 still contain an incomplete depth-limit bypass in
lib/helpers/toFormData.js. When serializing an object with a top-level key ending in{}, axios callsJSON.stringify()on that value before theformSerializer.maxDepthguard can inspect the nested structure.An attacker who can control object keys and nested values passed by an application into axios form or parameter serialization can trigger a raw
RangeError: Maximum call stack size exceeded, causing a denial of service in the affected request path.Impact
The impact is availability only. No confidentiality or integrity impact was confirmed.
Server-side applications are the primary concern when they accept user-controlled input and pass it into axios as
dataorparamsformultipart/form-data,application/x-www-form-urlencoded, or default parameter serialization. Browser impact is limited to the page or request context unless the application builds a broader failure mode around the thrown exception.The attack requires control over a top-level object key ending in
{}and a deeply nested object value. The optionformSerializer.metaTokens: falseis not a workaround because it only changes the emitted key name; the value is still stringified.Affected Functionality
Affected paths include:
lib/helpers/toFormData.jswhen a top-level key ends with{}.lib/helpers/toURLEncodedForm.js, which delegates tohelpers.defaultVisitor.lib/helpers/AxiosURLSearchParams.js, used by default params serialization.lib/defaults/index.jswhen object data is serialized asmultipart/form-dataorapplication/x-www-form-urlencoded.Unaffected paths include:
FormDataorURLSearchParamsvalues that axios does not walk withtoFormData.paramsSerializer.serializeimplementations that do not call axiostoFormData.{}deeply nested values intoFormData, which hitERR_FORM_DATA_DEPTH_EXCEEDEDas intended.Technical Details
In
lib/helpers/toFormData.js,defaultVisitor()handles top-level keys ending in{}before recursive traversal:The depth guard is in
build():For
{}metatoken values,build()only sees the top-level property. The nested value is handed directly to nativeJSON.stringify(), which recurses internally and can throwRangeErrorbefore axios emits the intendedAxiosError.Proof of Concept of Attack
Safe local PoC with no network I/O:
Expected fixed behavior is an
AxiosErrorwith codeERR_FORM_DATA_DEPTH_EXCEEDED.Workarounds
Reject or depth-limit untrusted objects before passing them to axios serialization.
Strip or reject top-level keys ending in
{}from untrusted objects when using axios form serialization.For query parameters, use a custom
paramsSerializer.serializethat enforces a depth limit.For form bodies, construct
FormDataorURLSearchParamsmanually after validating input depth.Original Report
Summary
The
maxDepth=100guard added in axios 1.15.0 to fix GHSA-62hf-57xw-28j9 lives inside thebuild()recursion inlib/helpers/toFormData.js. The default visitor atlib/helpers/toFormData.js:166-170still has a top-level shortcut that callsJSON.stringify(value)whenever a ke