CVE-2026-58263: Jodit Editor Cross-Site Scripting Vulnerability
HIGHCVE-2026-58263 is a HIGH (CVSS 7.2) cross-site scripting vulnerability (CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')) affecting Jodit Editor libraries in the npm ecosystem. The issue involves mutation xss in jodit clean-html via a mathml/style rawtext carrier. Remediation is available by upgrading affected packages to version 4.12.28.
CVE-2026-58263 at a Glance
Security Impact
CVSS: 7.2
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N).
How to Fix CVE-2026-58263
To remediate this vulnerability, upgrade all affected Jodit Editor dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install jodit@4.12.28
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls jodit
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2026-58263, inspect your project dependencies using npm:
npm ls jodit
Analyze the terminal output to locate any version of Jodit Editor in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.12.28), your application is affected.
Note: The npm ls jodit command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting'), where the product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users. In Jodit Editor, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-58263 Affected Jodit Editor Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
jodit
|
npm |
< 4.12.28
|
4.12.28
|
References
Frequently Asked Questions
What is CVE-2026-58263?
Which Jodit Editor versions are affected?
What version fixes CVE-2026-58263?
Is jodit affected?
What is the CVSS score of CVE-2026-58263?
What CWE is associated with CVE-2026-58263?
How can I check npm dependencies for CVE-2026-58263?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-rxcw-mc6f-6hr3",
"modified" : "2026-07-31T19:12:40Z",
"published" : "2026-07-31T19:12:40Z",
"aliases" : [ "CVE-2026-58263" ],
"summary" : "Jodit Editor: Mutation XSS in jodit clean-html via a MathML/style rawtext carrier",
"details" : "### Summary\njodit's built-in `clean-html` sanitizer can be bypassed by a MathML/`<style>` carrier that hides a dangerous element from the sanitizer's element walk, so a no-interaction event handler survives into the editor value. When an application supplies attacker-influenced HTML to the editor's value-set or insertion paths, the sanitized output still contains a live `<img ... onload=...>` (or another non-`onerror` handler such as `onfocus`). A consumer that renders that output (`element.innerHTML = editor.value`) executes the handler with no user interaction. This is a stored cross-site scripting vulnerability, confirmed live on the shipped `es2021/jodit.min.js` for 4.12.25 and the latest 4.12.27 (in Chromium via a client-side `innerHTML` consumer, and in Firefox via server-rendered / document-context output; see the cross-browser note under Proof of concept).\n\n### Details\nThe bypass exploits the order in which `clean-html` parses, walks, and re-serializes the value.\n\n1. On the value-set path, the `clean-html` plugin handles `:beforeSetNativeEditorValue` (`src/plugins/clean-html/clean-html.ts:116`), parsing the value into an inert document: `sandBox.innerHTML = data.value`.\n\n2. In that parse, the source nesting `math > mtext > table > mglyph > style` triggers MathML text-integration-point and foster-parenting rules: the `<img>` is parsed as text inside `<style>` (rawtext), not as an element. The `<table>` is foster-parented out, and the `<mglyph>` MathML text-integration point governs the namespace, so the `<img>` never becomes an element node in this parse.\n\n3. That value-set sanitizer is `safeHTML` (`src/core/helpers/html/safe-html.ts:24`); it walks the tree but acts on elements only (the `Dom.isElement` gate at `:39`) and runs against the parse-1 `sandBox`, in which the `<img>` is rawtext, not an element. So `removeAllEventAttributes` (the full `on*` strip at `safe-html.ts:76`) has no element to clean and the handler passes through. The `onBeforeSetNativeEditorValue` handler runs `safeHTML` on that parse-1 tree both before and after it captures the value, so neither pass ever sees the `<img>` as an element.\n\n4. The captured value (`data.value = sandBox.innerHTML`) is then assigned to the editable, a second parse, which hoists the `<img>` out of `<style>` and into the HTML namespace as a live element with its handler intact. The serialize-reparse moves the element across the tree and across namespaces:\n```\nBEFORE - parse 1 (sandBox): the <img> is <style> text\n <math> [MathML]\n <mtext> [MathML]\n <mglyph> [HTML] integration point: content parses as HTML\n <style> [HTML] text \"<img ... onload=...>\" <-- <img> is RAWTEXT, not an element\n <table> [HTML]\n\nAFTER - editor.value (re-parsed): the <img> is hoisted OUT of <style>, live\n <p> [HTML]\n <math> [MathML]\n <mtext> [MathML]\n <mglyph> [MathML]\n <style> [MathML] (now empty)\n <img> [HTML] <-- hoisted out, HTML namespace, LIVE -> its handler fires\n <table> [HTML]\n```\n\n5. `editor.value` now carries that live element. The value-set pass (Steps 1-4) only walked the parse-1 `sandBox` and never sees it; the other sanitizer, the on-change visitor (`visitNodeWalker` via a `LazyWalker`, `clean-html.ts:56`/`:70`), does reach the hoisted element, but its `sanitizeAttributes` filter calls `sanitizeHTMLElement` (`safe-html.ts:139`), which strips `onerror` only - it never reads the `removeEventAttributes` flag `sanitizeAttributes` passes it (`sanitize-attributes.ts:30`), so it never runs the full `on*` strip. So `onload`, `onfocus`, and every other non-`onerror` handler is never removed and persists in `editor.value` permanently. (`onerror` is the one handler the cleaner removes, but only after a ~300ms window in which it too fires.)\n\nThe two code points (jodit 4.12.27):\n```js\n// 1. clean-html.ts onBeforeSetNativeEditorValue - the SYNCHRONOUS value-set pass runs on the parse-1 sandBox:\nsandBox.innerHTML = data.value; // :128 parse 1: the carrier hides the element as <style> rawtext\nthis.j.e.fire('safeHTML', sandBox); // :129 safeHTML element-walk misses the rawtext element\ndata.value = sandBox.innerHTML; // :130 value captured; re-parsing it into the editable hoists the element live\nsafeHTML(sandBox, { safeJavaScriptLink: true, removeOnError: true }); // :131 re-runs on the SAME parse-1 sandBox, never on the captured value\n\n// 2. the ASYNC on-change filter (LazyWalker) reaches the hoisted element, but only strips onerror:\nsanitizeHTMLElement(nodeElm, { /* ... */ removeEventAttributes: opts.removeEventAttributes }); // sanitize-attributes.ts:30 - passes the full-strip flag\nexport function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError } = { /* ... */ }) { // safe-html.ts:139 - never destructures removeEventAttributes\n if (removeOnError && elm.hasAttribute('onerror')) attr(elm, 'onerror', null); // onerror ONLY; onload / onfocus / ... are left live\n}\n```\n\nAll four layers are required: removing any of `math` + the integration point, `table`, the `mglyph` slot, or the rawtext element makes jodit strip the handler (substitutes per slot are under Carrier variants).\n\nThe four-layer carrier is required only on 4.11.2 and later. jodit 4.11.2 added `cleanHTML.removeEventAttributes` (the value-set full `on*` strip); before it (all 3.x and 4.0 through 4.10.x) the sanitizer only ever removed `onerror`, so on those versions a plain non-`onerror` handler such as `<img ... onload=...>` survives `editor.value` directly with no carrier (live-confirmed on 3.24.9, 4.0.1, 4.2.27). On 4.11.2 and later, the value-set walk strips the bare handler, so the carrier is needed to hide it as `<style>` rawtext past that walk; and because the on-change cleaner removes only `onerror` (Step 5), a non-`onerror` hoisted handler survives across the whole range.\n\nThe bypass is not specific to the value setter. The same carrier survives clean-html through `editor.value = X`, `editor.setEditorValue(X)`, and `editor.s.insertHTML(X)` (the API jodit's own documentation uses for plugins and custom buttons).\n\nThis is distinct from jodit's known XSS advisories: CVE-2023-42399 (GHSA-95xr-cq6h-vwr3) is an `iframe[src]` URL-scheme issue fixed in a 4.0.0 beta, and CVE-2022-23461 (GHSA-42hx-vrxx-5r6v) is a paste-from-Word `onerror` desanitization in `<= 3.24.2`. Neither involves this parse-1 rawtext-hoist mechanism, and a search of the issue tracker for mglyph / mathml / mutation / \"value xss\" finds no prior report.\n\n### Proof of concept\nDefault configuration. Assign the payload, read it back, render it the way a consumer would:\n```js\nconst editor = Jodit.make('#editor');\neditor.value = '<math><mtext><table><mglyph><style><img src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" onload=alert(document.domain)></style></mglyph></table></mtext></math>';\n\n// editor.value (the sanitized, stored output) now contains a live handler, and it persists (onload, like any\n// non-onerror handler, is not removed by jodit's on-change cleaner):\n// <p><math><mtext><mglyph><style></style></mglyph>\n// <img src=\"data:image/gif;base64,R0lGOD...AAIBRAA7\" onload=\"alert(document.domain)\">\n// <table></table></mtext></math></p>\n\ndocument.getElementById('view').innerHTML = editor.value; // a consumer renders the saved value\n// -> the 1x1 gif loads, onload fires alert(document.domain), no interaction\n```\nCross-browser: `editor.value` carries the hoisted live `<img>` in both Blink and Gecko, but the consumer's parse mode decides execution. Chromium fires it under both client-side `element.innerHTML` and document parsing; Firefox fires it only under document parsing (server-side rendering, `document.write`, `<iframe srcdoc>`), because under `innerHTML` Gecko leaves the `<img>` in the MathML namespace, inert. No iframe is required: a plain `innerHTML` consumer suffices in Chromium, any server-rendered consumer in Firefox.\n\nPositive control, same run: a plain `<img src=x onerror=alert(1)>`, a plain `<img onfocus=alert(1) autofocus tabindex=1>`, and a plain `<svg onload=alert(1)>` are all stripped by the synchronous value-set pass to harmless output, proving clean-html is active; the same handlers pass through only when carrier-hidden as `<style>` rawtext, so the contrast isolates the rawtext-hiding step. (A plain `<script>` is a separate case - the value-set pass does not remove tags; only the async on-change cleaner removes it ~300ms later - so it is not part of this synchronous control.)\n\nCarrier variants: three of the four layers accept substitutes: the MathML text-integration point (`mtext` / `mi` / `ms` / `mn` / `mo`), the integration-point child (`mglyph` / `malignmark`), and the rawtext element (`style` / `xmp` / `noembed` / `script` / `plaintext`; `title` / `textarea` / `noscript` do not work). `math` and `table` have no working substitute. The hidden element is not limited to `<img>`, and any non-`onerror` handler persists permanently. Element-name blocklisting will not close this.\n\n### Impact\nStored XSS with no user interaction, in the default configuration, on the input paths that jodit's own `clean-html` is responsible for sanitizing. jodit's own test suite asserts this is a sanitization boundary: `src/plugins/clean-html/clean-html.test.js` asserts that `editor.value = '<p>test <img src=\"\" onerror=\"alert(111)\" alt=\"\"></p>'` sanitizes to `<p>test <img src=\"\" alt=\"\"></p>` (the `onerror` removed) under the default config. The carrier in this report passes that same default-config sanitizer yet keeps the handler live, defeating the asserted guarantee. A prior fix in 4.12.21 addressed a stored XSS premised on an application re-rendering `editor.value` as trusted HTML, so this threat model is maintainer-acknowledged. Precondition: an attacker can place HTML into the editor (a content-submission role) and the editor output is later rendered. Any integration binding `value` to application state in jodit-react, loading a previously stored document, or inserting content via a plugin/button will execute attacker script in the victim's page.\n\n### Suggested fix\nRemove the gadget element at the source rather than re-sanitizing the output. A re-sanitize loop does not close this: nesting the carrier inside itself surfaces one level per parse, so one extra pass is bypassed at depth 2, and any fixed cap N is out-nested at depth N+2 (depth generalizes trivially). The robust fix is to drop any HTML-namespace element smuggled inside `<math>`/`<svg>` outside a spec integration point (`<foreignObject>`, `<annotation-xml>`, `<desc>`, `<title>`) during the element walk, the same approach DOMPurify's `_checkValidNamespace` uses. This is one pass and depth-independent. It must run on both entry points: the synchronous value-set `safeHTML` pass and the on-change walker (which does not call `safeHTML`). A regression test asserting that no `on*` survives a round-trip through `editor.value`, including the nested-carrier case, locks it in.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "jodit"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "4.12.28"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/xdan/jodit/security/advisories/GHSA-rxcw-mc6f-6hr3"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-58263"
}, {
"type" : "WEB",
"url" : "https://github.com/xdan/jodit/commit/0ebb61692cbe84f9abf10ac76dd594dbb6343b90"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/xdan/jodit"
}, {
"type" : "WEB",
"url" : "https://github.com/xdan/jodit/releases/tag/4.12.28"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-79", "CWE-83" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-31T19:12:40Z",
"nvd_published_at" : "2026-07-01T21:17:04Z"
}
}