Feb 26, 2026·6 min read·6 visits
Svelte 5's SSR error handling failed to escape `-->` sequences when serializing errors into HTML comments. Attackers can trigger an error containing malicious payloads to break out of the comment and execute XSS. Fixed in 5.53.5.
A Cross-Site Scripting (XSS) vulnerability exists in Svelte 5 versions prior to 5.53.5. The flaw occurs during Server-Side Rendering (SSR) when the framework attempts to serialize error objects into HTML comments for client-side hydration. Because the serialization process relied solely on `JSON.stringify()` without escaping HTML comment delimiters, an attacker can inject a closing comment tag (`-->`) to break out of the comment context and execute arbitrary JavaScript in the victim's browser.
Modern web frameworks have a love-hate relationship with the DOM. To make pages load fast, we use Server-Side Rendering (SSR) to spit out a full HTML page before the JavaScript bundle even wakes up. But once the JavaScript loads, it needs to 'hydrate' that static HTML—attach event listeners, build the virtual DOM, and figure out the state of the world.
Svelte 5, with its fancy new 'runes' system, handles this by embedding serialization markers directly into the HTML. When a component crashes during SSR (inside an error boundary), Svelte needs to tell the client: "Hey, something went wrong here, don't try to hydrate this part normally." To do this, it serializes the error object and stuffs it into an HTML comment. It's a clever trick—browsers ignore comments, so the layout doesn't shift until Svelte takes over.
But here's the problem with clever tricks: they often rely on assumptions. Svelte assumed that JSON.stringify() was safe enough to put inside an HTML comment. They forgot that the HTML parser and the JSON parser speak two very different languages, and in the gap between them, XSS lives.
This vulnerability is a classic case of "Context Confusion." In the world of JSON, the characters < and > are just boring string literals. JSON.stringify({ "msg": "-->" }) returns {"msg":"-->"}. It's valid JSON. It's safe JSON.
However, in the brutalist architecture of HTML, the sequence --> is the End of Comment delimiter. It doesn't matter if it's inside quotes, inside brackets, or inside a JSON object. If the HTML parser is reading a comment and sees -->, the comment ends. Immediately. Right there.
So, when Svelte takes that valid JSON and drops it into <!-- [JSON] -->, the browser parses it until it hits the attacker's -->. The rest of the JSON string is then dumped into the DOM as raw, executable markup. The developer trusted JSON.stringify to sanitize data, but JSON.stringify isn't an HTML sanitizer. It protects against JavaScript syntax errors, not HTML structure injection.
Let's look at the crime scene in Renderer.js. The vulnerable code was deceptively simple. It takes a transformed error object and pushes it into the output buffer wrapped in comments.
Vulnerable Code (Before):
// Inside the renderer logic
child.#out.push(`<!--${HYDRATION_START_FAILED}${JSON.stringify(transformed)}-->`);See the issue? It's a straight template literal injection. If transformed contains -->, the comment breaks. The fix, implemented in version 5.53.5, acknowledges that we are crossing a boundary between data and markup. We can't just stringify; we must escape the delimiters.
Fixed Code (After):
static #serialize_failed_boundary(error) {
var json = JSON.stringify(error);
// Manually escape angle brackets to their unicode equivalents
var escaped = json.replace(/>/g, '\\u003e').replace(/</g, '\\u003c');
return `<!--${HYDRATION_START_FAILED}${escaped}-->`;
}The fix is elegant. By converting < and > to \u003c and \u003e, the JSON remains valid (JSON parsers decode unicode escapes automatically), but the HTML parser no longer sees the structural characters that define tags or comments.
To exploit this, we need a specific setup: a Svelte 5 application using SSR, an Error Boundary, and a way to force an error that contains user-controlled input. Imagine a blog engine where a user can define a custom title that gets processed by a component.
The Attack Chain:
--> <script>alert(origin)</script> <!--.<!--{"message":"--> <script>alert(origin)</script> <!--"}-->.Here is how the browser sees the payload:
The trailing <!-- in the payload cleans up the mess, turning the real closing }--> of the JSON into a harmless comment, preventing syntax errors that might alert the user (or the console logs) too early.
This isn't just a client-side DOM XSS. This is Reflected XSS delivered via the initial server response. This distinction matters for a few reasons. First, it bypasses many client-side XSS filters that only look for malicious DOM manipulation after load. Second, because the payload arrives in the initial HTML document, it executes immediately—often before any client-side security frameworks or Content Security Policy (CSP) nonces might be fully hydrated or applied if they rely on JS to initialize (though a strict HTTP-header CSP would still block inline scripts).
If the application is an Admin Dashboard or a Social Platform using Svelte 5, this allows full account takeover. The attacker can steal HttpOnly-flagged cookies (if the XSS is used to proxy requests), read local storage, or perform actions as the victim. Given Svelte's popularity in modern, high-interactivity web apps, the attack surface is specific but high-value.
The remediation is straightforward: Update to Svelte 5.53.5 immediately. The Svelte team has patched the Renderer.js logic to handle the escaping natively.
If you cannot update (perhaps you are locked to a specific version for enterprise reasons), you must ensure that any custom error transformation logic sanitizes the input before Svelte sees it. However, this is risky. You would need to implement a transformError hook that recursively strips or escapes --> sequences from error messages. But let's be honest: you're going to miss an edge case. Just update the package.
> [!NOTE] > This vulnerability only affects apps using Server-Side Rendering (SSR). If you are building a pure Single Page App (SPA) where the backend only serves JSON APIs and the frontend is a static bundle, this specific injection path does not exist, as the DOM API used by the browser to create comments is not susceptible to delimiter collision in the same way raw HTML parsing is.
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:L/VI:N/VA:N/SC:H/SI:H/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Svelte Svelte | >= 5.53.0, < 5.53.5 | 5.53.5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network |
| CVSS Score | 5.3 (Medium) |
| Impact | Cross-Site Scripting (XSS) |
| Exploit Status | Proof of Concept (PoC) Available |
| Patch Status | Fixed in 5.53.5 |