Back to Blog
high SEVERITY6 min read

How Denial of Service via Prototype Pollution happens in Axios and how to fix it

Axios versions prior to 1.15.1 merged untrusted configuration objects without guarding against the `__proto__` key, letting attacker-controlled input pollute `Object.prototype` and crash or destabilize applications. Upgrading axios (and its transitive dependencies `form-data`, `follow-redirects`, `proxy-from-env`) closes this Denial of Service and prototype-pollution attack surface without changing any application code.

O
By Orbis AppSec
Published September 7, 2026Reviewed September 7, 2026

Answer Summary

CVE-2026-25639 is a Denial of Service vulnerability in the axios HTTP client caused by its `mergeConfig` function recursively merging objects without rejecting the `__proto__` key, mapped to CWE-1321 (Prototype Pollution). It's fixed by upgrading axios from 1.12.2 to 1.15.1 (and 0.31.1 for the 0.x line), which adds explicit guards against prototype-polluting keys during config merging.

Vulnerability at a Glance

cweCWE-1321 (Improperly Controlled Modification of Object Prototype Attributes)
fixUpgrade axios to 1.15.1 (and the 0.x line to 0.31.1), which sanitizes merge keys and updates dependent packages (`form-data`, `follow-redirects`, `proxy-from-env`)
riskAttacker-influenced config or request objects can pollute `Object.prototype`, crashing the process or corrupting shared object behavior across the app
languageJavaScript / Node.js
root causeaxios `mergeConfig` recursively copied properties from source config objects into targets without excluding `__proto__`, `constructor`, or `prototype` keys
vulnerabilityDenial of Service via Prototype Pollution (`__proto__` key in mergeConfig)

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.1mergeConfig rejects __proto__/constructor/prototype keys during merge.

Prevention & Best Practices

  • Never trust for...in or naive Object.assign-style merges on untrusted objects. Always allow-list expected keys or explicitly deny __proto__, constructor, and prototype.
  • 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/mergeDeepProperties internals, 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 pollute Object.prototype process-wide, not just the current request.
  • Bumping axios from 1.12.2 to 1.15.1 in package-lock.json (plus form-data, follow-redirects, proxy-from-env transitive 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 / mergeDeepProperties function in axios@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, or prototype during the recursive merge.
  • CWE: CWE-1321 — Improperly Controlled Modification of Object Prototype Attributes (Prototype Pollution).
  • Fix: Upgraded the axios dependency in package.json/package-lock.json from 1.12.2 to 1.15.1 (and the 0.x line to 0.31.1), which patches mergeConfig to 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

Frequently Asked Questions

What is Denial of Service via Prototype Pollution?

It's an attack where a malicious key like `__proto__` is injected into an object that gets merged or cloned, allowing the attacker to modify `Object.prototype` itself, which can crash the application or alter behavior across unrelated code paths.

How do you prevent prototype pollution in JavaScript?

Explicitly block `__proto__`, `constructor`, and `prototype` keys in any recursive merge/clone/assign function, use `Object.create(null)` for maps of untrusted keys, or rely on vetted libraries (like the patched axios) that already guard against this.

What CWE is Denial of Service via Prototype Pollution?

CWE-1321 — Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution').

Is Object.freeze(Object.prototype) enough to prevent prototype pollution?

It mitigates the impact by preventing writes to the prototype, but it's not a complete fix — it can break legitimate libraries and doesn't stop pollution of intermediate objects; input-key filtering at the merge function is the correct primary defense.

Can static analysis detect prototype pollution?

Yes — tools like Trivy, Semgrep, and npm audit can flag known-vulnerable dependency versions (as happened here), and dedicated dataflow rules can catch unguarded recursive merge patterns in custom code.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1181

Related Articles

high

How JavaScript Injection via String Interpolation Happens in Go Wails Applications and How to Fix It

A high-severity JavaScript injection vulnerability in `internal/clusterconfigs/input.go` allowed arbitrary code execution through malicious kubeconfig filenames. The `saveClusterConfigFile` function at line 20 constructed JavaScript code by directly interpolating unsanitized filenames into `window.ExecJS()` calls, enabling attackers to break out of string literals and execute arbitrary JavaScript in the Webview context.

critical

How Server-Side Request Forgery happens in Node.js and how to fix it

The order-flow service in a Node.js e-commerce backend built an outbound fetch() URL by directly concatenating a configurable `sendingOrder.url` value with a query string, with no validation of protocol or destination. This allowed order data—including customer and payment-adjacent information—to be silently redirected to an attacker-controlled endpoint simply by changing a config value or environment variable.

high

How Infinite Loop Denial of Service Happens in nanoid and How to Fix It

CVE-2026-67213 is a high-severity infinite loop vulnerability in nanoid's `customAlphabet` function that could cause Denial of Service through CPU exhaustion. The fix upgrades nanoid from 3.3.12 to patched versions 3.3.18 and 5.1.6, eliminating the loop condition that trapped ID generation when processing certain input patterns.

critical

How Message Corruption via Protocol Length Header Abuse Happens in WebSocket Implementations and How to Fix It

CVE-2026-54466 is a critical vulnerability in websocket-driver 0.7.4 that allows attackers to corrupt WebSocket messages by abusing protocol length headers. The fix upgrades the package to version 0.7.5, which implements proper validation of untrusted length header inputs. This vulnerability could allow attackers to modify or inject data into real-time communication channels used by frontend applications.

critical

How XML Entity Expansion happens in Node.js and how to fix it

A critical XML External Entity (XXE) vulnerability in `lib/xml2json.js` allowed attackers to trigger exponential memory consumption through nested entity expansion. The fix adds `strictEntities: true` to both SAX parser instances, disabling dangerous entity processing that could crash servers processing untrusted XML.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.