Jun 6, 2026·7 min read·11 visits
A high-severity Cross-Site Scripting (XSS) vulnerability (CVSS 8.7) in TinyMCE (versions >= 6.8.0, < 7.1.0) allows attackers to bypass HTML sanitization using nested SVGs and execute arbitrary JavaScript code inside a victim's browser.
TinyMCE versions 6.8.0 through 7.0.1 contain a high-severity Cross-Site Scripting (XSS) vulnerability. The flaw exists in the custom HTML parser and sanitizer module, which incorrectly manages SVG namespace scopes when parsing nested elements. A low-privileged or unauthenticated attacker can submit a crafted HTML payload containing nested SVG structures to bypass sanitization filters, leading to arbitrary JavaScript execution in the context of the victim's browser session.
TinyMCE is an industry-standard, open-source rich text editor widely integrated into web applications, content management systems, and enterprise portals. In version 6.8.0, TinyMCE introduced native support for parsing and validating Scalable Vector Graphics (SVG) elements to allow authors to insert rich graphic content directly within the editing panel. To ensure security, TinyMCE processes all inputted HTML through a custom, string-based sanitization pipeline prior to rendering.
This pipeline is managed by custom classes within the editor's architecture, primarily the html/Parser.ts and html/Sanitizer.ts modules. Rather than leveraging the host browser's native DOM parser, which can cause layout shifts and strip structural formatting, TinyMCE tokenizes and parses the input manually against a configured schema. This design creates a substantial attack surface, as any parsing mismatch between TinyMCE's internal serializer and the browser's native rendering engine can be exploited.
The vulnerability is classified under CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting'). It affects versions 6.8.0 through 7.0.1 (encompassing both the 6.8.x and 7.0.x release branches). By introducing specific nested SVG elements, an attacker can desynchronize the sanitizer's internal namespace tracking state from the parsing rules applied by the browser's HTML5 engine.
To understand the root cause, it is necessary to examine how web browsers handle namespaces under the HTML5 parsing specification. When a browser's HTML parser encounters an <svg> tag, it transitions from the default HTML namespace (http://www.w3.org/1999/xhtml) to the SVG namespace (http://www.w3.org/2000/svg). Elements within this scope are parsed as "foreign content" with distinct parsing behaviors.
The HTML5 specification defines standard integration points where the parser switches back to the HTML namespace. Inside an SVG, elements such as <desc>, <title>, and <metadata> act as HTML integration points. Any children of these tags transition back to the default HTML namespace. However, if a standard HTML element is placed directly inside an SVG scope without being wrapped in an integration point, the browser's parser immediately terminates the active SVG scope, pops the SVG namespace off its stack, and processes the element under HTML rules.
TinyMCE's custom sanitizer fails to implement this complex, stateful namespace stack. Instead, the parser tracked SVG namespace transitions using simplified state variables (such as a flat boolean flag or an incorrectly updated nesting counter). When the sanitizer encounters nested SVG elements combined with HTML integration points (such as <desc>), its internal state tracker becomes desynchronized from the browser's parser.
If the sanitizer registers a nested closing </svg> tag, it transitions its parsing state back to the HTML namespace, assuming the SVG block has terminated. However, the browser's parser remains inside the parent SVG namespace. This state mismatch allows an attacker to insert attributes that are normalized safely by the sanitizer but execute as arbitrary JavaScript when rendered inside the browser's real DOM.
Prior to the patch in version 7.1.0, TinyMCE's html/Parser.ts maintained a basic state machine to validate nodes against the schema. Below is a conceptual representation of the vulnerable state tracking compared to the corrected stack-based architecture.
// VULNERABLE LOGIC (Pre-7.1.0 Conceptual Representation)
class VulnerableParser {
private inSvg: boolean = false;
parseNode(node: Token) {
if (node.name === 'svg') {
this.inSvg = true; // Flattens nesting context
} else if (node.name === '/svg') {
this.inSvg = false; // Prematurely terminates on nested closing tags
}
// Sanitization decisions are made using the flawed state
if (this.inSvg) {
this.validateSvgAttribute(node);
} else {
this.validateHtmlAttribute(node);
}
}
}The corrected implementation introduced in 7.1.0 replaces simple boolean toggles and shallow counters with a robust stack tracker. This tracker perfectly mirrors the browser's namespace switching mechanics, validating HTML integration points like <desc> and <title> explicitly.
// PATCHED LOGIC (Post-7.1.0 Stack-Based Namespace Tracking)
class PatchedParser {
private namespaceStack: string[] = ['html'];
parseNode(node: Token) {
const currentNamespace = this.getCurrentNamespace();
if (node.name === 'svg') {
this.namespaceStack.push('svg');
} else if (node.name === '/svg') {
if (this.namespaceStack.includes('svg')) {
// Correctly pop until the corresponding SVG scope is resolved
this.popToScope('svg');
}
} else if (this.isHtmlIntegrationPoint(node.name) && currentNamespace === 'svg') {
this.namespaceStack.push('html');
} else if (node.name === `/${node.name}`) {
this.namespaceStack.pop();
}
this.validateNodeUnderNamespace(node, this.getCurrentNamespace());
}
private getCurrentNamespace(): string {
return this.namespaceStack[this.namespaceStack.length - 1] || 'html';
}
}The implementation of this stack ensures that the parser tracks the exact depth of the SVG context. This modification prevents mutated HTML content from causing the tokenizer to prematurely exit its sanitized state, neutralizing the desynchronization vector.
To exploit this vulnerability, an attacker must have permissions to submit HTML content to a web application that relies on TinyMCE for rich text rendering. This typically requires low-privileged credentials (such as a forum member or standard content contributor), though in some implementations, the editor is exposed to unauthenticated guest users.
An attacker can use a payload designed to trigger namespace confusion through nested tags and integration point attributes. Consider the following proof-of-concept payload:
<svg><svg><desc><p id="</svg><img src=x onerror=alert(document.domain)>"></p></desc></svg></svg>The exploitation flow proceeds as follows:
<desc>, its internal parsing state fails to recognize the HTML integration boundary correctly. The custom parser views </svg> inside the id attribute as an actual ending token instead of an attribute value. It incorrectly treats the following <img> tag as nested, valid HTML. Because the sanitizer assumes the browser will interpret this sequence safely, it processes the tags and outputs them without removing the dangerous onerror handler.<desc> as an HTML integration point. It parses <p> as a standard HTML element and evaluates id="</svg><img src=x onerror=alert(document.domain)>" as a literal, benign string attribute. However, if the payload undergoes subsequent serialization steps (such as mutation XSS operations inside the application), the browser's parser is forced to reconstruct the node, releasing the embedded <img> tag into the active namespace and executing the alert() script.The security impact of CVE-2026-47760 is evaluated as High. A successful exploit allows arbitrary JavaScript execution within the target's active session. This vector leads to several high-severity consequences, including session hijacking through the theft of unencrypted session cookies or LocalStorage tokens.
Additionally, attackers can perform unauthorized API operations on behalf of the victim. If an administrative user views the malicious content, the injected script can silently execute administrative functions, such as creating new privileged users, changing configuration settings, or exporting proprietary data.
The vulnerability has a CVSS v3.1 base score of 8.7. The scoring breakdown reflects standard high-impact network vulnerabilities: Network attack vector (AV:N), Low attack complexity (AC:L), Low privileges required (PR:L), and Required user interaction (UI:R). Because the script executes outside the boundaries of the text editor, the vulnerability changes the operational scope (S:C), resulting in High confidentiality and integrity compromises (C:H/I:H).
The primary and most effective remediation is upgrading the TinyMCE editor component to version 7.1.0 or higher. For applications still running on older branches, ensure the library is updated to a patched version that incorporates the stack-based parser changes.
If upgrading immediately is not technically feasible due to dependency lock-ins, developers can apply a temporary workaround by disabling SVG and MathML elements completely within the TinyMCE initialization parameters. This configuration forces the editor to discard the tags responsible for namespace desynchronization before they reach the parser module:
tinymce.init({
selector: 'textarea',
invalid_elements: 'svg,math,desc,title,metadata,foreignobject'
});Additionally, implementing a server-side sanitization layer acts as an effective secondary line of defense. The application backend should process all submitted user data using a secure, spec-compliant parsing library (such as DOMPurify) before storing it in persistent databases or outputting it to clients.
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
TinyMCE Tiny Technologies Inc. | >= 6.8.0, < 7.1.0 | 7.1.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network |
| CVSS v3.1 Score | 8.7 |
| EPSS Score | 0.00033 |
| Exploit Status | poc |
| KEV Status | Not Listed |
The application does not neutralize or incorrectly neutralizes user-controlled input before it is placed into web pages, allowing executable JavaScript code to run in a client browser session.
CVE-2026-47759 is a critical stored Cross-Site Scripting (XSS) vulnerability affecting multiple active branches of the TinyMCE rich text editor. The flaw resides in the editor's handling of user-controlled, prefixed internal attributes, such as data-mce-href, data-mce-src, and data-mce-style. When processing raw HTML inputs, TinyMCE's internal validation schema neglects to inspect these custom prefixed attributes. During HTML serialization, the editor's engine extracts these unsanitized values and copies them back into standard executable attributes, overwriting any previously sanitized standard values and leading to execution of arbitrary code.
A high-severity stored Cross-Site Scripting (XSS) vulnerability was identified in the TinyMCE rich text editor. The flaw exists in the handling of the 'protect' configuration option, where forged placeholder comments containing malicious payloads bypass the editor's sanitization routines and execute arbitrary JavaScript during serialization and content restoration.
An authorization bypass and client-side property tampering vulnerability (CVE-2026-47742) in the Shopper headless admin panel (built on Laravel and Livewire) allows low-privileged users to modify arbitrary product records (Insecure Direct Object Reference). This occurs due to unlocked public model properties and a complete lack of access control checks on mutating sub-form store methods.
Shopper is an open-source headless e-commerce administration panel built on Laravel, Livewire, and Filament. Prior to version 2.8.0, the admin tables for PaymentMethods, Currencies, and Carriers exposed inline toggles and per-record actions that could be modified by any authenticated user without verifying the corresponding administrative permissions on the backend.
An Insecure Direct Object Reference (IDOR) vulnerability in Bugsink (versions < 2.2.0) allows authenticated users with access to at least one project to view sensitive event details (including stack traces, local/environment variables, and execution breadcrumbs) belonging to other projects, by supplying a known event UUID directly to the issue event URL paths.
Bugsink prior to version 2.2.0 is vulnerable to Broken Object Level Authorization (BOLA). The issue list view authorizes access based on the project in the URL path but applies requested bulk actions to submitted issue UUIDs globally, without verifying project ownership.