Feb 18, 2026·5 min read·37 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')
Prior to version 1.2.11, the LangChain LLM framework is affected by a Server-Side Request Forgery (SSRF) vulnerability inside its image token counting mechanism. Specifically, the ChatOpenAI.get_num_tokens_from_messages() method retrieves arbitrary image_url values from user prompts without validating the destination host or IP address. Attackers can exploit this issue to scan internal infrastructure, access local services, or harvest credentials from cloud metadata services.
A security vulnerability was identified in the Astro web framework's composable integration pipeline with Hono. Due to the structural coupling of CSRF origin validation exclusively within the `middleware()` primitive, applications assembling their routing pipeline manually could execute state-mutating actions before or entirely without origin validation. This flaw allows attackers to execute blind, write-only Cross-Site Request Forgery (CSRF) attacks against state-mutating Astro Actions or endpoints on behalf of authenticated users.
Multiple cross-site request forgery (CSRF) vulnerabilities in the HTTP Administration component in Cisco IOS 12.4 on the 871 Integrated Services Router allow remote attackers to execute arbitrary commands via crafted HTTP requests. This occurs because the web administrative server fails to validate request origins or use anti-CSRF tokens, allowing an attacker to abuse an active administrative session.
An unbounded resource allocation vulnerability exists in Guzzle's cookie parsing and storage engine. Prior to version 7.15.1, Guzzle did not restrict the number or size of cookies stored within the client-side CookieJar. This lack of boundary control allows a malicious server or an intermediary proxy to poison the cookie storage with oversized or excessive Set-Cookie headers. When the client subsequently targets sibling domains or legitimate services using the same cookie jar, it transmits exceptionally large Cookie headers, triggering upstream protocol violations and resulting in Denial of Service (HTTP 431 / HTTP 400 rejection).
An information disclosure vulnerability in guzzlehttp/guzzle allows host-only cookies to be incorrectly matched and sent to subdomains. Because Guzzle failed to track whether cookies were defined without a Domain attribute, it evaluated host-only cookies using standard domain-suffix rules, widening their scope and exposing sensitive session tokens to untrusted subdomains.
An issue in Guzzle's RedirectMiddleware allows client-side URI fragments to be leaked over the network in Referer headers during same-scheme HTTP redirects.