Severity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H
Exploitation requires an in-process prototype-pollution gadget (local code-execution state), so AV:L/PR:L/AC:H; header injection gives I:L, crash gadget gives A:H, no confidentiality impact.
Primary rating from NVD.
CVSS VectorNVD
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H
Lifecycle Timeline
8Blast Radius
ecosystem impact- 273 npm packages depend on axios (189 direct, 84 indirect)
Ecosystem-wide dependent count for version 1.0.0.
DescriptionNVD
Summary
axios 1.15.2 exposes two read-side prototype-pollution gadgets. When Object.prototype is polluted by an upstream dependency in the same process (e.g. lodash _.merge / CVE-2018-16487), axios silently picks up the polluted values:
- Header injection -
lib/utils.jsline 406 buildsmerge()'s accumulator asresult = {}, soresult[targetKey](line 414) walksObject.prototypeand the polluted bucket's own keys are copied into the merged headers and ride out on the wire. - Crash DoS -
lib/core/mergeConfig.jsline 26 builds thehasOwnPropertydescriptor as a plain-object literal.Object.definePropertyreadsdescriptor.get/descriptor.setvia the prototype chain, so a pollutedObject.prototype.getorObject.prototype.setmakes the call throwTypeErrorsynchronously on every axios request.
Affected Properties
| Polluted slot | Effect |
|---|---|
Object.prototype.common | injects headers on every method |
Object.prototype.delete / .head / .post / .put / .patch / .query | injects headers on the matching method |
Object.prototype.get | every axios request throws TypeError: Getter must be a function from mergeConfig.js:26 |
Object.prototype.set | every axios request throws TypeError: Setter must be a function from mergeConfig.js:26 |
Per-request headers (axios.request(url, { headers: {...} })) overwrite polluted entries. Polluting Object.prototype.get triggers the crash before any header is built.
Proof of Concept
const axios = require('axios');
// Finding A - header injection
Object.prototype.common = { 'X-Poisoned': 'yes' };
await axios.get('http://api.example.com/users');
// Wire request carries `X-Poisoned: yes`.
// Finding B - crash DoS
Object.prototype.get = { something: 'anything' };
await axios.get('http://api.example.com/users');
// TypeError: Getter must be a function: #<Object>
// at Function.defineProperty (<anonymous>)
// at mergeConfig (lib/core/mergeConfig.js:26:10)Impact
- Server hang (
Content-Length: 99999): receiver waits for a body that never arrives. Affects requests with a body. - CL+TE conflict (
Transfer-Encoding: chunkedrides alongside axios's autoContent-Length): receiver rejects with400 Bad Request. Affects requests with a body. - Response suppression (
If-None-Match: *): receiver returns empty304 Not Modified. Affects GET / HEAD. - Crash DoS (
Object.prototype.get/.set): every axios request fails synchronously withTypeError, notAxiosError, so handlers filtering onerror.isAxiosErrormishandle the failure.
Attack Flow
flowchart TD
ROOT["Polluted Object.prototype<br/>via upstream gadget (e.g. lodash <= 4.17.10 _.merge / CVE-2018-16487)<br/>axios <= 1.15.2"]
ROOT --> CLASS_A["A. Arbitrary HTTP Header Injection<br/>Polluted defaults.headers slot rides along on every outbound axios request"]
ROOT --> CLASS_B["B. Crash DoS via Object.prototype.get / .set<br/>Polluted descriptor breaks Object.defineProperty in mergeConfig"]
CLASS_A --> PRE_A["Precondition: header not set per-request by the app<br/>Injected via defaults.headers slot<br/>(common, delete, head, post, put, patch, query)"]
PRE_A --> PA1["Response Suppression<br/>Trigger: common = {If-None-Match: *}<br/>Affects GET / HEAD"]
PA1 --> SA1["DoS<br/>304 Not Modified empty"]
PRE_A --> PA2["Server Hang<br/>Trigger: common = {Content-Length: 99999}<br/>Affects requests with body"]
PA2 --> SA2["DoS<br/>connection hang"]
PRE_A --> PA3["CL+TE Conflict<br/>Trigger: common = {Transfer-Encoding: chunked}<br/>Affects requests with body"]
PA3 --> SA3["DoS<br/>400 Bad Request"]
CLASS_B --> SB1["DoS<br/>TypeError: Getter / Setter must be a function<br/>Crashes every axios request, not only GET"]
%% Styles
style ROOT fill:#f87171,stroke:#991b1b,color:#fff
style CLASS_A fill:#fb923c,stroke:#9a3412,color:#fff
style CLASS_B fill:#fb923c,stroke:#9a3412,color:#fff
style PRE_A fill:#e2e8f0,stroke:#64748b,color:#1e293b
style PA1 fill:#fbbf24,stroke:#92400e,color:#000
style PA2 fill:#fbbf24,stroke:#92400e,color:#000
style PA3 fill:#fbbf24,stroke:#92400e,color:#000
style SA1 fill:#ef4444,stroke:#991b1b,color:#fff
style SA2 fill:#ef4444,stroke:#991b1b,color:#fff
style SA3 fill:#ef4444,stroke:#991b1b,color:#fff
style SB1 fill:#ef4444,stroke:#991b1b,color:#fffRoot Cause
Finding A. lib/utils.js:404-429's merge() creates result = {} at line 406. The dangerous-keys filter on lines 408-411 blocks the write side, but the read at line 414 (isPlainObject(result[targetKey])) still walks the prototype chain. When targetKey matches a polluted slot, result[targetKey] returns the polluted nested object, and the recursive merge(result[targetKey], val) on line 415 iterates that object's own keys via forEach and copies them as own properties into the new accumulator. Those keys flow through mergeConfig.js:35 → Axios.js:148 (utils.merge(headers.common, headers[config.method])) → Axios.js:155 (AxiosHeaders.concat(...)) → onto the wire via http.js:677 (headers: headers.toJSON()) → http.js:767 (transport.request(options, ...)).
Finding B. lib/core/mergeConfig.js:25 correctly makes config = Object.create(null), but the descriptor passed on line 26 is a plain-object literal - its get/set lookups walk Object.prototype. A polluted non-function Object.prototype.get or .set makes Object.defineProperty throw TypeError: Getter must be a function (or Setter must be a function) before the call returns. The descriptor is built unconditionally on every mergeConfig invocation, so every axios request throws - POST, PUT, DELETE, PATCH, HEAD, QUERY, not only GET.
Suggested Fix
Use null-prototype objects in place of the plain-object literals at lib/utils.js:406 and lib/core/mergeConfig.js:26-31. The same descriptor pattern recurs at lib/core/AxiosError.js:37, lib/core/AxiosHeaders.js:100, lib/utils.js:447/454/492/498, and lib/adapters/adapters.js:28/32.
Resources
- CVE-2018-16487 -
lodash.mergeprototype pollution inlodash <= 4.17.10 - CWE-1321 - Improperly Controlled Modification of Object Prototype Attributes
Articles & Coverage 1
AnalysisAI
Denial-of-service and HTTP header injection in the axios npm package (>=1.0.0,<1.16.0 and <=0.31.1) arise from two read-side prototype-pollution gadgets in lib/utils.js merge() and lib/core/mergeConfig.js, allowing a polluted Object.prototype (typically from an upstream dependency such as lodash CVE-2018-16487) to inject arbitrary outbound headers or crash every axios call with a synchronous TypeError. Publicly available exploit code exists in the GHSA advisory PoC, but EPSS is only 0.04% (13th percentile) and SSVC marks Automatable=no with partial technical impact, so this is a credible secondary-gadget risk rather than a mass-exploitation target.
Technical ContextAI
The defect is a CWE-1321 (Improperly Controlled Modification of Object Prototype Attributes) read-side gadget in axios's request-config and header-merge pipeline. Finding A: lib/utils.js:406 builds merge()'s accumulator as a plain {} literal, so the line 414 read result[targetKey] walks Object.prototype; if Object.prototype.common (or .delete/.head/.post/.put/.patch/.query) is polluted, its own keys are copied into the merged headers and flow through mergeConfig.js:35 → Axios.js:148 → AxiosHeaders.concat → http.js:677 toJSON onto the wire. Finding B: lib/core/mergeConfig.js:26 passes a plain-object descriptor to Object.defineProperty, whose internal [[GetOwnProperty]]/lookups walk the prototype chain for get/set, so a polluted non-function Object.prototype.get or .set makes the call throw TypeError on every request, regardless of method. The CPE pkg:npm/axios identifies the JavaScript HTTP client used by Node services and browser bundles; the gadget activates only when something else in the same process pollutes Object.prototype.
RemediationAI
Vendor-released patch: upgrade axios to 1.16.0 (for the 1.x branch) or 0.32.0 (for the 0.x branch) per https://github.com/axios/axios/security/advisories/GHSA-898c-q2cr-xwhg. In parallel, eliminate the upstream pollution gadget - audit the dependency tree for vulnerable lodash (<=4.17.10, CVE-2018-16487) and similar merge/set utilities, and upgrade or replace them. As a short-term compensating control while patching is staged, freeze Object.prototype at process start with Object.freeze(Object.prototype) (trade-off: breaks any library that monkey-patches built-ins), set every per-request header explicitly via axios.request(url, { headers: {...} }) to overwrite polluted defaults bucket entries (does not mitigate the Finding B crash, which fires before headers are built), and add a runtime guard such as a require-time check that Object.prototype.get / .set / .common are undefined. Generic 'isAxiosError' filtering should be widened, since Finding B surfaces as a raw TypeError rather than AxiosError.
More in Prototype Pollution
View allPrototype pollution in Adobe Acrobat Reader versions 24.001.30356, 26.001.21367 and earlier enables arbitrary code execu
Prototype pollution in the farinspace Partners WordPress plugin (versions up to and including 0.2.0) enables remote unau
Prototype pollution vulnerability in 'deephas' versions 1.0.0 through 1.0.5 allows attacker to cause a denial of service
A Prototype Pollution issue in Cronvel Tree-kit v.0.7.4 and before allows a remote attacker to execute arbitrary code vi
A Prototype Pollution issue in Aliconnect /sdk v.0.0.6 allows an attacker to execute arbitrary code via the aim function
All versions of the package safe-eval are vulnerable to Prototype Pollution via the safeEval function, due to improper s
This affects the package vm2 before 3.9.4 via a Prototype Pollution attack vector, which can lead to execution of arbitr
Prototype Pollution in chargeover redoc v2.0.9-rc.69 allows attackers to execute arbitrary code or cause a Denial of Ser
alizeait unflatto <= 1.0.2 was discovered to contain a prototype pollution via the method exports.unflatto at /dist/inde
A vulnerability exists in the 'dagre-d3-es' Node.js package version 7.0.9, specifically within the 'bk' module's addConf
A Prototype Pollution vulnerability in the util-deps.addFileDepend function of magix-combine-ex versions thru 1.2.10 all
Chartist 1.x through 1.3.0 allows Prototype Pollution via the extend function. Rated critical severity (CVSS 9.8), this
Same technique Denial Of Service
View allVendor StatusVendor
Share
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-36260
GHSA-898c-q2cr-xwhg