CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Dashboard
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-27901
5.30.04%

Svelte SSR XSS: When innerText Betrays You

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 27, 2026·5 min read·8 visits

PoC Available

Executive Summary (TL;DR)

Svelte's SSR compiler failed to escape data bound to `innerText` and `textContent` on `contenteditable` elements. This allows attackers to inject malicious HTML/JS scripts that execute when the page is rendered on the server. Fixed in v5.53.5.

In the world of web security, `innerText` is supposed to be the good guy—the safe alternative to the chaotic evil of `innerHTML`. Developers are taught that assigning text to `innerText` automatically escapes HTML entities, neutralizing Cross-Site Scripting (XSS) attacks before they start. But CVE-2026-27901 flips the script. In Svelte versions prior to 5.53.5, the Server-Side Rendering (SSR) engine treated `bind:innerText` on `contenteditable` elements as raw HTML injection. This effectively turned a safety feature into a direct pipeline for remote code execution in the victim's browser, proving once again that in SSR, everything is just string concatenation waiting to go wrong.

The Server-Side Illusion

We often forget that Server-Side Rendering (SSR) is, at its core, a glorified string concatenation engine. When you write a component in a modern framework like Svelte, you aren't manipulating a DOM; you are instructing a compiler to build a massive string of HTML to chuck at the browser. In the browser (the client), the DOM API is your bodyguard. If you set element.innerText = "<script>", the browser dutifully converts that to &lt;script&gt;. You are safe.

But on the server? There is no DOM. There is only text. The framework has to simulate that safety by manually running escape functions on every piece of dynamic data before stitching it into the HTML string. If the framework developers miss just one spot, that safety evaporates.

This vulnerability is a classic case of that simulation breaking down. Svelte's compiler, usually rigorous about sanitization, had a blind spot for contenteditable elements. It assumed that if you were binding data to an editable area, you probably wanted the raw content, effectively treating innerText (the safe one) exactly like innerHTML (the dangerous one). It’s like labeling a bottle of nitroglycerin "Water" because they're both liquids.

The Flaw: Identity Crisis

The root cause lies deep within the Svelte compiler's transformation phase, specifically in how it handles attributes during SSR. The logic serves to take your high-level Svelte code and transpile it into efficient JavaScript that generates HTML strings.

When the compiler encountered a contenteditable element, it had to decide how to render the initial state. The logic checked if the binding was for innerText, textContent, or innerHTML. Tragically, the code grouped all three together. It assumed that because contenteditable allows rich text editing in some contexts, the developer must want the raw HTML output for all bindings.

This is a logic error, not a buffer overflow or a memory corruption. The compiler simply failed to apply the $.escape() utility to innerText and textContent bindings. It passed the raw expression directly into the final HTML output buffer. So, when the server renders <div contenteditable bind:innerText={userInput}>, it doesn't output <div>&lt;script&gt;...</div>; it outputs <div><script>...</div>.

The Smoking Gun

Let's look at the code responsible for this betrayal. The vulnerability lived in packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js. This file dictates how elements are constructed during the server-side build.

Before the fix, the code looked something like this:

// The "Oops" Moment
if (is_content_editable_binding(attribute.name)) {
    // Whether it's innerHTML, innerText, or textContent,
    // it just dumps the raw expression into content.
    content = expression;
}

It’s almost insultingly simple. The fix involves explicitly checking the attribute name and applying the escape function if it's not innerHTML.

After the fix (v5.53.5):

if (attribute.name === 'innerHTML') {
    // innerHTML stays raw (dangerous by design)
    content = expression;
} else if (
    is_content_editable_binding(attribute.name) ||
    (attribute.name === 'value' && node.name === 'textarea')
) {
    // EVERYTHING ELSE GETS ESCAPED
    content = b.call('$.escape', expression);
}

That b.call('$.escape', expression) is the difference between a functional website and a compromised user base.

The Exploit: Scripting the Server

Exploiting this requires a scenario where you render user-controlled input into a contenteditable div using SSR. This is common in collaborative editing apps, CMS interfaces, or rich-text comment sections that want to be SEO-friendly.

Here is the attack chain:

  1. The Injection: An attacker submits a profile bio or comment containing: <img src=x onerror=alert(document.cookie)>.
  2. The Storage: The backend saves this string to the database.
  3. The Rendering: A user visits the profile page. The server fetches the string and renders the Svelte component.

Vulnerable Component:

<script>
  let bio = $props.bio; // "<img src=x onerror=..."
</script>
 
<!-- The trap is set -->
<div contenteditable bind:innerText={bio}></div>
  1. The Execution: Svelte generates the HTML string. Because of the bug, the output is: <div contenteditable><img src=x onerror=alert(document.cookie)></div>

  2. The Payload: The victim's browser parses this HTML. It sees the img tag, tries to load x, fails, and triggers the onerror event, executing the JavaScript. The attacker now has the victim's session tokens.

Why Should We Panic?

While the CVSS score is a "Medium" 5.3, do not let that lull you into a false sense of security. The score is dampened by the complexity (AC:H) required—specifically, you need a contenteditable element bound to user input and SSR enabled. That is not every app, but it is exactly the kind of setup used by modern, rich web applications (think Notion clones, document editors, or social platforms).

If you are hit by this, the impact is full Stored XSS. This isn't just an alert box; it's session hijacking, forced actions, or phishing via DOM manipulation. Furthermore, because the payload comes from the server's initial HTML response, it executes immediately upon page load, often before client-side frameworks or hydration logic can intervene. It bypasses any client-side sanitization logic you might have running in onMount because the damage is done before the component even mounts.

The Fix: Upgrade or Die

The remediation is straightforward: Update svelte to version 5.53.5 immediately.

If you cannot update right now (perhaps you enjoy living dangerously or have strict change freezes), you must audit your codebase for bind:innerText or bind:textContent on elements that also have the contenteditable attribute.

Workaround: Stop binding directly. Instead, sanitize the data before passing it to the component context, or use a manual binding approach that doesn't rely on the vulnerable SSR path. But really, just update the package. It's a patch-level update designed specifically to fix this.

Official Patches

SvelteCommit 0df5abc: Fix SSR escaping for contenteditable bindings

Fix Analysis (1)

Technical Appendix

CVSS Score
5.3/ 10
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
EPSS Probability
0.04%
Top 86% most exploited

Affected Systems

Svelte Framework (npm package)

Affected Versions Detail

Product
Affected Versions
Fixed Version
svelte
sveltejs
< 5.53.55.53.5
AttributeDetail
CVE IDCVE-2026-27901
CWE IDCWE-79 (XSS)
CVSS Score5.3 (Medium)
Attack VectorNetwork
ImpactCross-Site Scripting (XSS)
Fixed Version5.53.5

MITRE ATT&CK Mapping

T1189Drive-by Compromise
Initial Access
T1059.007Command and Scripting Interpreter: JavaScript
Execution
CWE-79
Cross-site Scripting

Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

Known Exploits & Detection

GitHub AdvisoryOfficial PoC demonstrating contenteditable bypass

Vulnerability Timeline

Patch Committed to Main Branch
2026-02-25
CVE Published
2026-02-26
GHSA Advisory Released
2026-02-26

References & Sources

  • [1]GHSA-phwv-c562-gvmh
  • [2]NVD - CVE-2026-27901

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.