Introduction
This Denial of Service vulnerability could have allowed attackers to crash Node.js processes or silently corrupt shared object behavior across an entire application — simply by getting a specially crafted __proto__ key into a configuration object that axios later merges. The flaw lived deep inside axios's internal mergeConfig utility, the function every axios request uses to combine default configuration with per-request options (headers, params, baseURL, transformers, and more).
In this repository, the dependency was pinned to axios 1.12.2 in package-lock.json, a version that predates the fix for this exact issue. Any code path where request configuration is built from user-influenced data — think a proxy service that forwards client-supplied headers or params into an axios call — could be exposed to this class of bug even though no application code directly referenced __proto__.
The Vulnerability Explained
Axios builds its final request configuration by recursively merging several config sources together (defaults, instance config, and per-call overrides). Internally, mergeConfig walks the keys of each source object and copies them onto a target object:
// simplified representation of the vulnerable merge pattern
function mergeDeepProperties(target, source) {
for (const key in source) {
target[key] = source[key]; // no check for "__proto__"
}
return target;
}
The problem is that for...in (and naive Object.keys copies) will happily iterate over a key literally named __proto__ if it exists as an own enumerable property on the source object — which is exactly what happens when that object was created via JSON.parse() on attacker-controlled input, such as:
{ "__proto__": { "isAdmin": true, "toString": "polluted" } }
If a caller passes an object like this as part of axios request config (for example, merging user-supplied query params or headers into the config before calling axios(config)), the assignment target[key] = source[key] effectively becomes target.__proto__ = { ... }, mutating Object.prototype for the entire Node.js process — not just the current request.
Example attack scenario: Imagine a backend service that exposes an endpoint accepting a JSON body used to build an outbound axios request, e.g. axios.request(mergeConfig(defaults, userSuppliedOptions)). An attacker submits userSuppliedOptions containing a __proto__ key with a nested object designed to:
- Overwrite Object.prototype.toString or hasOwnProperty, breaking unrelated code that relies on default prototype behavior, or
- Inject unexpected enumerable properties onto every plain object in the app, causing infinite loops or unexpected branches in code that iterates for...in over objects — leading to CPU exhaustion and a Denial of Service.
Because the pollution lands on Object.prototype, the blast radius isn't scoped to the axios call — it can affect authentication checks, template rendering, or any other logic elsewhere in the same Node.js process that trusts default object behavior.
The Fix
The remediation here isn't a hand-written patch to application code — it's a dependency upgrade that pulls in the upstream axios fix. The diff in package-lock.json shows the actual change:
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
- "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.1.tgz",
+ "integrity": "sha512-WOG+Jj8ZOvR0a3rAn+Tuf1UQJRxw5venr6DgdbJzngJE3qG7X0kL83CZGpdHMxEm+ZK3seAbvFsw4FfOfP9vxg==",
"dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.4",
- "proxy-from-env": "^1.1.0"
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
+ "proxy-from-env": "^2.1.0"
}
Inside axios 1.15.1, the internal merge logic (mergeConfig / mergeDeepProperties) now explicitly filters out dangerous keys — __proto__, constructor, and prototype — before copying properties, and iterates safely so a config object crafted by an attacker can no longer reach Object.prototype. This closes the exact code path described in the CVE ("Denial of Service via __proto__ Key in mergeConfig") without axios changing its public API — valid configuration objects merge exactly as before.
The lockfile update also pulls in form-data 4.0.6 (bumping its hasown and mime-types ranges) and follow-redirects 1.15.11, both routine hardening releases bundled into the same upgrade. None of these transitive bumps alter axios's request/response behavior for legitimate use — the PR description correctly notes the change "tightens handling of untrusted input and leaves valid inputs unaffected."
Before: axios@1.12.2 → vulnerable mergeConfig, no key filtering.
After: axios@1.15.1 → mergeConfig rejects __proto__/constructor/prototype keys during merge.
Prevention & Best Practices
- Never trust
for...inor naiveObject.assign-style merges on untrusted objects. Always allow-list expected keys or explicitly deny__proto__,constructor, andprototype. - Parse untrusted JSON defensively. Consider
JSON.parse(str, reviver)with a reviver that strips dangerous keys, or use libraries with built-in prototype-pollution protection. - Keep dependencies current. This fix required zero application code changes — just a version bump. Automated dependency scanning (Dependabot, Renovate, Trivy,
npm audit) catches these before they reach production. - Freeze what you can.
Object.freeze(Object.prototype)in defense-in-depth setups can block writes, though it should complement — not replace — proper input filtering. - Reference CWE-1321 when triaging similar findings so the fix targets the actual merge/clone function rather than surface symptoms.
Key Takeaways
- The vulnerable pattern lived in axios's
mergeConfig/mergeDeepPropertiesinternals, not in this repo's own source — a reminder that transitive dependency code runs with the same trust as your own. - A single
__proto__key in a JSON-derived config object was enough to polluteObject.prototypeprocess-wide, not just the current request. - Bumping
axiosfrom1.12.2to1.15.1inpackage-lock.json(plusform-data,follow-redirects,proxy-from-envtransitive updates) fully remediates the flagged path with no code changes required. - Any service that builds axios request config from user-supplied JSON (headers, params, proxy settings) should treat this class of CVE as high-priority even if "not confirmed reachable" by static scanning.
How Orbis AppSec Detected This
- Source: Configuration objects built from user- or client-influenced JSON (e.g., request headers, query params, or proxy settings) passed into axios's config merging pipeline.
- Sink: axios's internal
mergeConfig/mergeDeepPropertiesfunction inaxios@1.12.2, which recursively copied source object keys onto the target config without filtering__proto__. - Missing control: No key allow-listing or explicit rejection of
__proto__,constructor, orprototypeduring the recursive merge. - CWE: CWE-1321 — Improperly Controlled Modification of Object Prototype Attributes (Prototype Pollution).
- Fix: Upgraded the
axiosdependency inpackage.json/package-lock.jsonfrom1.12.2to1.15.1(and the 0.x line to0.31.1), which patchesmergeConfigto reject prototype-polluting keys.
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
This case is a textbook example of why dependency hygiene is a first-class security control, not an afterthought. The vulnerable code — a recursive object merge missing a __proto__ guard — never appeared in this project's own source files, yet it shipped with every axios request made through version 1.12.2. Upgrading to axios 1.15.1 (and 0.31.1 on the legacy branch) eliminates the Denial of Service and prototype-pollution risk in mergeConfig with zero behavioral change for legitimate requests. Treat CVE alerts on widely-used HTTP clients like axios as urgent, keep automated dependency scanning in your pipeline, and remember that "not confirmed reachable" doesn't mean "not exploitable" — it means the attack surface exists and should be closed proactively.
References
- CWE-1321: Improperly Controlled Modification of Object Prototype Attributes
- OWASP Cheat Sheet: Prototype Pollution Prevention
- Axios documentation: axios GitHub repository
- Semgrep rule reference: Prototype Pollution rules on Semgrep Registry
- Pull Request: fix: upgrade axios to 1.15.1, 0.31.1 (CVE-2026-42033)