Update dependency axios to v1.18.0 [SECURITY]#399
Open
renovate[bot] wants to merge 1 commit into
Open
Conversation
renovate
Bot
force-pushed
the
renovate/npm-axios-vulnerability
branch
from
July 24, 2026 21:33
9c88f40 to
b6cf57c
Compare
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: 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: Deep formToJSON Key Recursion Can Cause Denial of Service
GHSA-pmv8-rq9r-6j72
More information
Details
Summary
Axios versions starting with
0.28.0contain uncontrolled recursion informDataToJSON, which is exposed asaxios.formToJSON()and used internally when axios serialisesFormDatawithContent-Type: application/json.If an application passes attacker-controlled
FormDatafield names to this functionality, a field name with thousands of nested bracket segments can exhaust the JavaScript call stack and cause denial of service for that request or, in applications without appropriate error handling, process termination.Impact
Applications are affected only when untrusted users can control
FormDatakey names that are converted through axios.Affected paths include direct use of
axios.formToJSON()on untrustedFormDataand axios requests in which attacker-controlledFormDatais sent withContent-Type: application/json.The observed failure is
RangeError: Maximum call stack size exceeded. In local testing, this error is catchable, so process-wide crash depends on the consuming application's error handling and runtime behaviour.Affected Functionality
Affected functionality:
axios.formToJSON(formData)formToJSONtransformRequestbehaviour forFormDatawhenContent-Typecontainsapplication/jsonUnaffected functionality:
FormDatasubmission without JSON serialisationtoFormData, which already enforces amaxDepthguard<=0.27.2, whereformDataToJSONwas not presentTechnical Details
The vulnerable code is in
lib/helpers/formDataToJSON.js.parsePropPath()splits a field name such asa[x][x][x]into path segments.buildPath()then recursively processes one segment per call without enforcing a maximum depth:A key with thousands of bracket-delimited segments causes thousands of recursive calls and can exceed the JavaScript engine's call stack limit.
Relevant source locations:
lib/helpers/formDataToJSON.jscontains the unbounded recursivebuildPath().lib/axios.jsexposes the helper asaxios.formToJSON.index.jsexposesformToJSONas a named export.index.d.tsandindex.d.ctsdeclare the public API.lib/defaults/index.jscallsformDataToJSON(data)when JSON-serializingFormData.The inverse helper,
toFormData, already enforcesmaxDepthand throwsAxiosErrorwithERR_FORM_DATA_DEPTH_EXCEEDED, butformDataToJSONdoes not have an equivalent guard.Proof of Concept of Attack
Expected result on affected versions:
RangeError: Maximum call stack size exceeded
The same condition can be reached via an axios request transformation when attacker-controlled
FormDatais sent withContent-Type: application/json.Workarounds
Applications can reject or normalise untrusted form field names before calling
axios.formToJSON().Applications can avoid sending untrusted
FormDatathrough axios as JSON unless JSON conversion is required.Applications should catch errors around
formToJSON()or axios requests that transform untrustedFormData.Original Source
Summary
An uncontrolled recursion vulnerability in
formDataToJSONallows any user who controls FormData input to crash a Node.js process with a single request. The function recurses once per bracket-delimited segment in a FormData key name with no depth limit, so a key likea[x][x][x]...with 15,000+ segments exhausts the call stack. This is a denial-of-service that kills the process via an unrecoverableRangeError. The inverse functiontoFormDataalready enforces amaxDepthlimit (default 100) for exactly this reason —formDataToJSONlacks the equivalent guard.Details
Vulnerable function:
buildPathinlib/helpers/formDataToJSON.js, lines 50–82.buildPath(path, value, target, index)is called recursively — once per segment in the parsed property path — with no depth check:The key is first split into segments by
parsePropPath(line 17), which extracts every[segment]via regex. A key with 15,000 bracket pairs produces a 15,001-element array, causing 15,001 recursive calls — well beyond the V8 default stack limit (~10,000–15,000 frames).formDataToJSONis a public API consumed two ways:Directly by consumers — exported as
axios.formToJSON()(lib/axios.js:80), with TypeScript declarations in bothindex.d.ts:699andindex.d.cts:708, and documented in the API reference in four languages (docs/pages/advanced/api-reference.md).Internally by
transformRequest— called atlib/defaults/index.js:56when the request body isFormDataandContent-Typecontainsapplication/json:Contrast with
toFormData: The inverse function (lib/helpers/toFormData.js:118) enforcesmaxDepth(default 100) and throwsAxiosErrorwith codeERR_FORM_DATA_DEPTH_EXCEEDEDwhen exceeded.formDataToJSONhas no equivalent protection.PoC
Requires only Node.js and an unmodified axios v1.x install:
Verified output on Node.js 22.22.3 against axios v1.16.1 (current
v1.xHEAD):The process crashes. In a server context (e.g., Express middleware calling
axios.formToJSON()on an uploaded form), a single crafted request terminates the process.Impact
Denial of Service (process crash). Any unauthenticated user who can submit FormData to a Node.js application that passes it through
axios.formToJSON()— or that sends it as a JSON-serialized FormData body via axios — can crash the server process with a single request. TheRangeErrorfrom stack exhaustion is unrecoverable in many contexts (it cannot be reliably caught when the stack is already full). No authentication or special privileges are required; the attacker only needs to control a FormData key name.Severity
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 the GitHub Advisory Database (CC-BY 4.0).
Axios: 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 the GitHub Advisory Database (CC-BY 4.0).
Axios: 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