May 18, 2026·6 min read·2 visits
A sanitize-then-decode flaw in Sveltia CMS allows stored XSS. Attackers can inject entity-encoded HTML that bypasses sanitizers and executes when administrators view entry summaries.
Sveltia CMS versions prior to 0.160.1 contain a stored cross-site scripting (XSS) vulnerability within the content summary rendering subsystem. The flaw arises from an improper sequence of text transformation operations, specifically a sanitize-then-decode logic error. Attackers with content creation privileges can exploit this vulnerability by submitting entity-encoded HTML payloads, which execute malicious scripts within the browser context of users viewing the administrative interface.
Sveltia CMS is a Git-based headless content management system that provides a user interface for editing repository-hosted site content. The application relies on a content parsing subsystem to generate previews and summaries of Markdown-formatted entries. The vulnerability, identified as GHSA-97R8-RF7Q-WMJW, resides within this specific summary rendering component.
The vulnerability constitutes a Stored Cross-Site Scripting (XSS) condition, tracked under CWE-79. The flaw allows authenticated contributors to embed malicious JavaScript within repository entries. These scripts execute in the context of the Sveltia administrative interface when other users, such as high-privileged administrators, view the entry list.
The root cause stems from an architectural logic error in the text transformation sequence. The application processes user input using an improper ordering of sanitization and entity decoding operations. This specific execution sequence negates the security guarantees provided by the HTML sanitizer, enabling unauthorized script execution within the application domain.
The vulnerability originates in the sanitizeEntrySummary function located in src/lib/services/contents/entry/summary.js. This function processes text strings to generate secure HTML output for preview rendering. The function applies three primary transformations: inline Markdown parsing, HTML sanitization, and HTML entity decoding.
In versions prior to 0.160.1, the implementation utilized a flawed sanitize-then-decode execution order. The application invoked the sanitization routine before executing the HTML entity decoding function. The HTML sanitizer relies on identifying literal angle brackets (< and >) to strip unauthorized tags according to its whitelist.
When an attacker supplies input containing HTML entities, such as < and >, the sanitizer evaluates the string as harmless literal text. The sanitizer completes its execution pass without stripping the payload. Subsequently, the application calls parseEntities, which converts these safe entities back into their raw HTML character representations.
The resulting string is passed to the frontend rendering pipeline. Because the entity decoding occurs as the final step in the transformation chain, the application generates a live DOM node from the injected string. This bypasses the whitelist rules entirely and results in script execution.
The original implementation of the sanitizeEntrySummary function unconditionally called sanitize followed by parseEntities. This logical flow meant that the sanitizer operated on encoded data, while the application rendered the decoded output. The vulnerability was resolved in commit 43a6ac5d0182a503400d8ce1ac156e08f537b1b2.
The patched implementation introduces conditional ordering based on the allowMarkdown parameter:
export const sanitizeEntrySummary = (str, { allowMarkdown = false } = {}) => {
str = /** @type {string} */ (parseInline(str));
if (allowMarkdown) {
str = parseEntities(str);
}
str = sanitize(str, { ALLOWED_TAGS: allowMarkdown ? ['strong', 'em', 'code'] : [] });
if (!allowMarkdown) {
str = parseEntities(str);
}
return str.trim();
};By executing parseEntities(str) prior to sanitize(str) when Markdown is enabled, the sanitizer evaluates the decoded HTML string. This ensures that any maliciously encoded tags are converted to literal tags before the whitelist logic applies, allowing the sanitizer to accurately strip unapproved elements.
Security analysis of the patch indicates a residual risk remains for specific code paths. The patch maintains the vulnerable decode-after-sanitize order for summaries where allowMarkdown evaluates to false. If the content management system renders these non-Markdown summaries in an HTML-aware context, the vulnerability persists because the empty whitelist sanitization occurs before entity decoding.
Exploitation requires the attacker to possess commit access to the underlying Git repository or permissions to create content entries within the CMS. The attacker must inject the payload into fields utilized by the entry summary template. Typical vectors include the title, abstract, or description fields of a content entry.
The attacker constructs a payload utilizing HTML entity encoding to evade the initial sanitization pass. A standard vector targets the onerror attribute of an image tag. The attacker submits the payload <img src=x onerror="alert(document.cookie)"> within the targeted field.
The application processes the submitted entry upon the next synchronization or content update. The backend passes the inline content through the flawed sanitizeEntrySummary pipeline. The sanitizer ignores the encoded payload, and the subsequent entity decoding phase reconstructs the active HTML elements.
The exploitation chain concludes when an administrator navigates to the entry list within the Sveltia CMS dashboard. The browser parses the summary using DOM insertion techniques, instantiating the injected image tag. The source resolution fails, triggering the onerror event handler and executing the attacker's JavaScript within the administrator's session.
Successful exploitation grants the attacker arbitrary JavaScript execution within the origin of the Sveltia CMS application. The attacker gains full access to the Document Object Model, browser session storage, and administrative interface features. This facilitates session hijacking and unauthorized data access.
The secondary impact affects the underlying Git repository and the published website. An attacker who compromises an administrator's session can issue unauthorized commits to the repository via the application's internal API. This capability enables the attacker to modify site content, deface web pages, or inject backdoors into the compiled static site generation pipeline.
The vendor assesses the severity of this vulnerability as Low, correlating with the prerequisite of authenticated content creation. The threat model dictates that an attacker must already possess contributor credentials. However, the vulnerability enables vertical privilege escalation from a standard contributor to a full repository administrator via targeted exploitation.
The primary remediation strategy requires administrators to upgrade the Sveltia CMS deployment to version 0.160.1 or a subsequent release. This version incorporates the patched logic that correctly sequences entity decoding before sanitization for Markdown-enabled summaries. Administrators must verify the deployment version across all operational environments.
Organizations should implement a strict Content Security Policy (CSP) to provide defense-in-depth against cross-site scripting attacks. The CSP should restrict inline script execution by omitting the unsafe-inline directive. This architectural control mitigates the impact of any residual stored XSS vulnerabilities by preventing the browser from executing injected payload strings.
Security teams should proactively review Git commit histories and entry templates for suspicious content. Operators must monitor for content entries containing specific HTML entity encodings, such as < or ", within fields used for summary generation. Identification of these patterns within standard text fields serves as an indicator of potential exploitation attempts.
| Product | Affected Versions | Fixed Version |
|---|---|---|
Sveltia CMS Sveltia | < 0.160.1 | 0.160.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network (Authenticated) |
| CVSS Score | N/A (Low) |
| Impact | Stored XSS / Privilege Escalation |
| Exploit Status | Proof of Concept |
| KEV Status | Not Listed |
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')