Feb 18, 2026·5 min read·11 visits
Svelte 5 versions 5.46.0 through 5.46.2 are vulnerable to XSS during Server-Side Rendering. The framework uses unsafe JSON serialization for hydration keys, allowing attackers to break out of `<script>` tags using a `</script>` payload. Update to 5.46.3 immediately.
A classic 'breakout' vulnerability in Svelte 5's server-side rendering logic allows attackers to inject arbitrary JavaScript via the 'hydratable' function. By trusting 'JSON.stringify' to sanitize data embedded in HTML script tags, the framework inadvertently allows malicious payloads to terminate the script block and execute code in the victim's browser context.
Svelte 5 is the new hotness. It brought us 'runes', fine-grained reactivity, and a promise of better performance. But with great power comes great... complexity in the serialization layer. Specifically, we're looking at the Server-Side Rendering (SSR) mechanism. When you render a page on the server, the client needs to 'hydrate' that page—essentially waking up the static HTML and attaching JavaScript event listeners to it.
To do this, Svelte needs to pass data from the server to the client. It does this by embedding serialized data directly into the HTML, usually inside a <script> tag in the document head. This is a critical handover point. If the server messes up this game of 'telephone,' the client browser interprets the data as executable code rather than passive information.
CVE-2025-15265 is exactly that mess-up. It resides in the hydratable function, a feature used to persist state across the server-client boundary. The vulnerability isn't in your application code; it's deep inside how Svelte decides to write that data to the DOM. It's a classic case of trusting a standard library function (JSON.stringify) to do a security job it was never designed for.
Here is the fundamental rule of web security that developers forget once every generation: The HTML parser runs before the JavaScript parser.
When a browser receives an HTML document, it scans for tags. If it sees a <script> tag, it switches to 'script mode' until it sees the sequence </script>. The browser is dumb. It does not care if that closing tag is inside a JavaScript string, a comment, or a JSON object. If it sees </script>, it closes the block. Period.
Svelte's engineers made a fatal assumption. They assumed that if they ran JSON.stringify() on a key, it would be safe to drop into a script block.
// Ideally, this should happen:
var data = "<script>alert(1)</script>";
// Browser sees a string. Life is good.
// But if you are injecting it via server-side string concatenation:
<script>
var data = "</script><script>alert(1)</script>";
</script>In the vulnerable code, Svelte takes a user-controlled key, stringifies it, and dumps it right into the HTML. Because JSON.stringify does not escape the forward slash / or the less-than symbol < by default, a payload containing </script> successfully tells the browser: 'End the script here.' The text immediately following that—controlled by the attacker—is then rendered as raw HTML, allowing for a new malicious <script> tag to open.
Let's look at the crime scene in packages/svelte/src/internal/server/renderer.js. The vulnerable logic was handling the serialization of entries for the hydration map.
// Svelte 5.46.0 - 5.46.2
// k is the key provided to hydratable()
// v is the value associated with it
entries.push(`[${JSON.stringify(k)},${v.serialized}]`);See that JSON.stringify(k)? That's the kill shot. If k is the string "</script>", the output becomes ["</script>", .... The browser reads the HTML, hits that closing tag inside the string, and terminates the script context immediately.
The Svelte team, credited to the discovery by Camilo Vera, replaced the native JSON serializer with devalue.uneval. The devalue library is specifically designed to emit JavaScript that is safe to embed in HTML.
// Fixed in 5.46.3
import * as devalue from 'devalue';
// ...
entries.push(`[${devalue.uneval(k)},${v.serialized}]`);devalue.uneval turns < into \u003C and / into \u002F (or similar escapes) ensuring the browser never sees the literal sequence </script>.
Exploiting this is trivially easy if you can control the key passed to hydratable. Imagine a scenario where a Svelte app uses a URL parameter or a search query as a hydration key.
<!-- main.svelte -->
<script>
import { hydratable } from "svelte";
// 'key' comes from user input (e.g., URL param)
let { key } = $props();
// The vulnerability triggers here during SSR
hydratable(key, () => { ... });
</script>An attacker sends a link to the victim:
https://vulnerable-app.com/?key=</script><script>alert(document.cookie)</script>
key is </script><script>alert(document.cookie)</script>.JSON.stringify(key). Result: "\"</script><script>alert(document.cookie)</script>\"".<script>
const h = (window.__svelte ??= {}).h ??= new Map();
// The injection happens inside this array structure
h.set(["</script><script>alert(document.cookie)</script>", ...]);
</script></script> inside the string.<script>alert(document.cookie)</script> as a new script block.The remediation path is straightforward: Update Svelte immediately.
Fixed Version: 5.46.3
If you are stuck on a vulnerable version for some god-forsaken reason (enterprise change freezes, am I right?), you technically could sanitize the input before passing it to hydratable, but that is a losing game. You will miss edge cases.
> [!NOTE]
> Content Security Policy (CSP)
> A strict CSP is your best safety net here. If your policy forbids inline scripts (script-src 'self'), this attack fails because the injected script block is inline and unsigned. However, Svelte apps often rely on inline scripts for hydration bootstrapping, so ensuring your CSP is configured correctly (using nonces or hashes) is critical.
Takeaway for Developers: Never assume JSON.stringify makes data safe for HTML embedding. It makes data safe for JavaScript contexts, but HTML context is a different beast entirely. Use libraries like devalue or serialize-javascript that explicitly handle HTML entity escaping.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Svelte Svelte | >= 5.46.0, < 5.46.3 | 5.46.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network |
| CVSS Score | 6.1 (Medium) |
| Impact | Cross-Site Scripting (XSS) |
| Vulnerable Component | server/renderer.js (hydratable) |
| Exploit Status | PoC Available |
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')