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



GHSA-88QP-P4QG-RQM6
7.50.04%

SvelteKit Remote Functions: The Cost of Experimental Features

Alon Barad
Alon Barad
Software Engineer

Feb 19, 2026·6 min read·3 visits

PoC Available

Executive Summary (TL;DR)

If you enabled 'experimental.remoteFunctions' in SvelteKit < 2.52.2, your server is vulnerable to DoS. A malformed binary form payload can force the server into an expensive recursive type coercion loop.

An experimental feature in SvelteKit introduced a severe Denial of Service vulnerability via the binary form deserialization logic. By exploiting how the 'devalue' library handles object references and type coercion, an attacker can trigger massive CPU consumption.

The Hook: Shiny Toys and Hidden Traps

Developers love experimental features. It's the software equivalent of an 'Uncharted Territory' sign—irresistible. SvelteKit introduced remoteFunctions to allow seamless RPC-like calls between the client and server. To make this efficient, they needed something better than JSON. Enter devalue, a library that handles complex types like Map, Set, and cyclic references that JSON.stringify would choke on.

But here's the kicker: to support file uploads within this remote function architecture, SvelteKit implemented a custom binary deserializer for application/x-sveltekit-formdata. This deserializer reads a stream of bytes and reconstructs the form data, including file metadata. It's clever, efficient, and, as it turns out, completely trusting of the input it receives.

The vulnerability lies in how this deserializer reconstructs File objects. It assumes the metadata provided by the client (name, size, type) is well-formed. Spoiler alert: in the world of security, assuming input is well-formed is like leaving your front door unlocked because you live in a 'nice neighborhood'.

The Flaw: Type Coercion Hell

The root cause here is a classic JavaScript foot-gun: Implicit Type Coercion. When the deserialize_binary_form function processes the incoming byte stream, it encounters a definition for a File. It expects an array containing metadata: [name, type, size, lastModified, index].

The code then attempts to verify that the file data doesn't exceed the content length of the request. It does this with a simple arithmetic check:

// The code blindly trusts 'size' is a number
if (files_start_offset + file_offsets[index] + size > content_length) {
    throw deserialize_error('file data overflow');
}

See the problem? In JavaScript, the + operator and > comparison are not strictly typed. If size is not a number, the engine will try to make it a number. If an attacker provides a complex object (like a deeply nested array or a proxy to a huge BigInt) instead of a simple integer, the JavaScript engine triggers valueOf() or toString().

Because devalue allows for defining complex, recursive object graphs, an attacker can craft a payload where size references a massive, self-referential structure. The engine burns 100% CPU trying to coerce this monstrosity into a primitive number just to evaluate that if statement.

The Code: The Smoking Gun

Let's look at the diff. It's a perfect example of "we forgot to check types." The fix, introduced in version 2.52.2, essentially says: "I don't care what you think you are, you better be a number."

Here is the vulnerable logic versus the patched logic in packages/kit/src/runtime/form-utils.js:

Before (Vulnerable):

File: ([name, type, size, last_modified, index]) => {
    // Yolo. Let's do math on whatever 'size' is.
    if (files_start_offset + file_offsets[index] + size > content_length) {
        throw deserialize_error('file data overflow');
    }
    return new File(/*...*/);
}

After (Patched):

File: ([name, type, size, last_modified, index]) => {
    // The bouncer checks ID at the door.
    if (
        typeof name !== 'string' ||
        typeof type !== 'string' ||
        typeof size !== 'number' ||
        typeof last_modified !== 'number' ||
        typeof index !== 'number'
    ) {
        throw deserialize_error('invalid file metadata');
    }
 
    if (files_start_offset + file_offsets[index] + size > content_length) {
        throw deserialize_error('file data overflow');
    }
    // ...
}

The fix is boring, effectively just a block of typeof checks. But boring is safe. Boring is reliable.

The Exploit: Recursive Math of Doom

To exploit this, we don't need a buffer overflow or a ROP chain. We just need to understand devalue serialization format. We construct a payload where the size property of a file definition points to a recursive array structure.

Here is a conceptual breakdown of the attack vector:

  1. Define a Recursive Array: Create an array that contains references to itself or deeply nested children.
  2. Define a File: Create a file definition where the 3rd element (size) is a reference to that array.
  3. Send the Request: POST this to an endpoint handling remoteFunctions.

When the server parses this, it hits the size > content_length check. The engine tries to convert that recursive array to a number. It hangs.

// Conceptual PoC Payload Generator
const payload = JSON.stringify([
    // Index 0: The trap. A recursive structure.
    [...Array(100)].map(() => 0), // Base array
    // ... complex cross-references ...
    
    // The Trigger: A File object definition
    // [name, type, size (pointing to recursive structure), lastMod, index]
    ['malicious.txt', 'text/plain', ['BigInt', 1e100], 123456789, 0]
]);

The actual PoC found in the regression tests uses a mix of deep arrays and BigInt coercions to maximize the CPU spin. Since Node.js is single-threaded for the event loop, one such request effectively kills the server for everyone else.

The Impact: Why Should We Panic?

This is a Denial of Service (DoS) vulnerability. In a microservices environment, losing one node might be annoying. But for a monolithic SvelteKit application, this is fatal.

Because the exhaustion happens synchronously during the request parsing phase (before your business logic even runs), you cannot catch this with standard application-level try/catch blocks easily. The event loop locks up. Health checks fail. Kubernetes kills the pod. The pod restarts. The attacker sends another request.

It's an asymmetric attack: The attacker spends almost zero resources sending a small JSON payload, while your server spends 100% CPU trying to figure out if [Array(10000)] > 100.

The Fix: Remediation

The remediation is straightforward. If you are using @sveltejs/kit versions prior to 2.52.2, you have two options:

  1. Upgrade: Update to version 2.52.2 immediately. This includes the patch that enforces strict type checking on deserialized file metadata.
  2. Disable Experimental Features: If you cannot upgrade, check your svelte.config.js. If you have experimental: { remoteFunctions: true }, set it to false or remove it. This feature is opt-in, so if you didn't explicitly turn it on, you are safe.

Always treat experimental flags as exactly what they are: code that hasn't been battle-hardened yet.

Official Patches

SvelteOfficial patch commit

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

@sveltejs/kit

Affected Versions Detail

Product
Affected Versions
Fixed Version
@sveltejs/kit
Svelte
< 2.52.22.52.2
AttributeDetail
CWE IDCWE-400 (Uncontrolled Resource Consumption)
Attack VectorNetwork (Remote)
CVSS Score7.5 (High)
ImpactDenial of Service (CPU Exhaustion)
Exploit StatusPoC Available
Affected ComponentremoteFunctions / devalue deserializer

MITRE ATT&CK Mapping

T1499.003Endpoint Denial of Service: Application or System Exploitation
Impact
CWE-400
Uncontrolled Resource Consumption

Known Exploits & Detection

GitHubRegression test case demonstrating the DoS payload structure.

Vulnerability Timeline

Vulnerability Disclosed
2026-01-15
Patch Developed & Merged
2026-02-18
Version 2.52.2 Released
2026-02-18

References & Sources

  • [1]GitHub Advisory
  • [2]Release Notes 2.52.2

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.