{"id":"CVE-2026-62324","revision":null,"records":[{"revision":"5682b2f66f3b424d869513457ed1aa41","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-62324","aliases":[],"title":"Jodit has incomplete javascript: scheme normalization in sanitizeHTMLElement href check that allows link XSS","description":"Jodit Editor is a WYSIWYG editor with a built-in file browser & image editor. Prior to 4.12.31, Jodit's sanitizeHTMLElement method fails to use isDangerousUrl to normalize javascript: href values before checking the scheme, allowing case variants, control-byte prefixes, and embedded tabs or newlines to bypass filtering and execute attacker-controlled script when a victim clicks a stored link rendered by an application. This issue is fixed in version 4.12.31.","state":0,"publishedAt":"2026-07-31T19:08:03.043+00:00","modifiedAt":"2026-08-03T17:26:47.204+00:00","affected":[{"ecosystem":"Vendor product","name":"jodit","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"xdan\",\n                    \"product\": \"jodit\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 4.12.31\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N","score":5.4,"severity":"MEDIUM"}],"references":["https://github.com/xdan/jodit/security/advisories/GHSA-j839-gqq4-gf9j","https://github.com/xdan/jodit/commit/5fba6ef2381d151d7cb8e3c5ad0b9996af0f97b0","https://github.com/xdan/jodit/releases/tag/4.12.31"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-62324","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-62324","identifiers":["CVE-2026-62324"]},"retained":false,"historicalAffected":[]},{"revision":"eef764298565418aacdf6ad9cb9826ef","observedAt":"2026-10-25T14:19:28.838+00:00","statement":{"source":"nvd","id":"CVE-2026-62324","aliases":[],"title":"Jodit Editor is a WYSIWYG editor with a built-in file browser & image editor. Prior to 4.12.31, Jodit's sanitizeHTMLElement method fails to use isDangerousUrl to normalize javascript: href values before checking the scheme, allowing case va…","description":"Jodit Editor is a WYSIWYG editor with a built-in file browser & image editor. Prior to 4.12.31, Jodit's sanitizeHTMLElement method fails to use isDangerousUrl to normalize javascript: href values before checking the scheme, allowing case variants, control-byte prefixes, and embedded tabs or newlines to bypass filtering and execute attacker-controlled script when a victim clicks a stored link rendered by an application. This issue is fixed in version 4.12.31.","state":0,"publishedAt":"2026-07-31T20:16:53.197+00:00","modifiedAt":"2026-10-23T23:10:00.157+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N","score":5.4,"severity":"MEDIUM"}],"references":["https://github.com/xdan/jodit/commit/5fba6ef2381d151d7cb8e3c5ad0b9996af0f97b0","https://github.com/xdan/jodit/releases/tag/4.12.31","https://github.com/xdan/jodit/security/advisories/GHSA-j839-gqq4-gf9j"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-62324","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-79","CWE-83"],"productCriteria":[],"sourceDates":{"published":"2026-07-31T20:16:53.197","lastModified":"2026-10-23T23:10:00.157"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-62324","identifiers":["CVE-2026-62324"]},"retained":false,"historicalAffected":[]},{"revision":"df26fa43796d4abd8c1018999d718e05","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-j839-gqq4-gf9j","aliases":["CVE-2026-62324"],"title":"Jodit has incomplete javascript: scheme normalization in sanitizeHTMLElement href check that allows link XSS","description":"### Summary\n\njodit's `sanitizeHTMLElement` neutralizes a `javascript:` `href` using a bare `href.trim().indexOf('javascript') === 0` check. This omits the normalization jodit applies to every other URL attribute: `isDangerousUrl` strips control bytes with `value.replace(/[\\u0000-\\u0020]+/g, '')` and lowercases the value before testing the scheme. Because the `href` check does neither, it is bypassed by three obfuscation classes, all confirmed firing on click against the shipped 4.12.30 build:\n\n1. Case variants: `JAVASCRIPT:`, `Javascript:`, `jaVaScRiPt:` (the check is case-sensitive).\n2. A leading C0 control byte, e.g. a `\\x01` prefix before lowercase `javascript:` (`trim()` does not remove bytes in the `\\x00`-`\\x08` / `\\x0e`-`\\x1f` range, but the browser strips a leading control byte before resolving the scheme).\n3. An embedded tab or newline inside the scheme, e.g. `java\\tscript:` or `java\\nscript:` (the browser strips tab/newline from a URL, but `indexOf('javascript')` sees the broken word and does not match).\n\nThe dangerous href survives `editor.value =` assignment and the on-change LazyWalker, persisting in the stored editor value. A victim who clicks the link in any consumer that renders the stored value (readonly editor, server-rendered page, `innerHTML` consumer) runs attacker-controlled JS in that page's origin.\n\n### Details\n\nThe check is in `sanitizeHTMLElement` at `src/core/helpers/html/safe-html.ts:213`:\n\n```js\nif (safeJavaScriptLink && href && href.trim().indexOf('javascript') === 0) {\n    attr(elm, 'href', location.protocol + '//' + href);\n    effected = true;\n}\n```\n\n`href.trim()` removes leading/trailing ASCII whitespace only, and `indexOf('javascript')` is case-sensitive and literal. So the check fails to fire whenever the scheme is upper/mixed-case, prefixed by a non-whitespace control byte, or split by an embedded tab/newline - all of which a browser still resolves to `javascript:` on click (URI schemes are case-insensitive per RFC 3986 section 3.1; leading control bytes, tabs and newlines are stripped from a URL during parsing).\n\nThe same file already contains the correct routine, `isDangerousUrl()` (line 176), used for every other URL attribute (`src`, `data`, `action`, `formaction`, `poster`, `background`, `xlink:href`):\n\n```js\nfunction isDangerousUrl(value, tagName) {\n    const normalized = value.replace(/[\\u0000-\\u0020]+/g, '').toLowerCase();\n    if (/^(?:javascript|vbscript|livescript|mocha):/.test(normalized)) {\n        return true;\n    }\n    // ...\n}\n```\n\n`isDangerousUrl` strips every control byte and ASCII space (`/[\\u0000-\\u0020]+/g`) and lowercases before testing the scheme, so it resists all three obfuscations. But `href` never goes through it: the attribute list `isDangerousUrl` is applied to (`URL_ATTRIBUTES`) is commented \"besides href\", and `href` is handled only by the weaker `indexOf` check. Both the synchronous value-set path (`onBeforeSetNativeEditorValue` -> `safeHTML` -> `sanitizeHTMLElement`) and the asynchronous on-change path (`sanitizeAttributes` -> `sanitizeHTMLElement`) use that same weak check.\n\nPositive controls (filter is otherwise live): a plain lowercase `javascript:` href IS neutralized: jodit rewrites the value to `location.protocol + '//' + href`, so it reads `about://javascript:...` on an about:blank test page and `https://javascript:...` on an https page. A leading ASCII space or tab IS caught by `trim()`; the bypass is specific to the un-normalized forms above.\n\n### Proof of concept\n\nDefault configuration. Assign a payload to the editor and read back the stored value:\n\n```js\nconst editor = Jodit.make('#editor');\neditor.value = '<a href=\"JAVASCRIPT:alert(document.domain)\">click me</a>';\n// editor.value getter returns the href unchanged:\n//   <p><a href=\"JAVASCRIPT:alert(document.domain)\">click me</a></p>\ndocument.getElementById('view').innerHTML = editor.value;\n// Clicking \"click me\" runs alert(document.domain) in the consumer's origin.\n```\n\nThe same persists for the leading-control-byte form (a `\\x01` prefix before lowercase `javascript:`) and the embedded-tab/newline forms (`java\\tscript:` / `java\\nscript:`). Verified live on the shipped `es2021/jodit.min.js` for jodit 4.12.30. Positive controls in the same run: `<img src=x onerror=...>` stripped; lowercase `javascript:` neutralized to `location.protocol + '//' + href` (`about://...` on the about:blank test page used here, `https://...` on an https page).\n\n### Impact\n\nStored click-XSS. An attacker with write access to an editor instance (content author, or comment author in a multi-user application) stores a crafted `javascript:` link. Any user who clicks it in a view that renders the stored value (readonly editor, server-rendered page, `innerHTML` consumer) runs attacker-controlled JS in that origin. One user interaction (the click) is required. A consumer that re-sanitizes the editor output before rendering is not affected.\n\n### Suggested fix\n\nRoute `href` through the existing `isDangerousUrl()` rather than the bespoke `indexOf` check. `isDangerousUrl` already strips control bytes and lowercases, so it closes the case, control-byte, and embedded-whitespace bypasses at once:\n\n```diff\n-\tif (safeJavaScriptLink && href && href.trim().indexOf('javascript') === 0) {\n+\tif (safeJavaScriptLink && href && isDangerousUrl(href, elm.nodeName.toLowerCase())) {\n \t\tattr(elm, 'href', location.protocol + '//' + href);\n \t\teffected = true;\n \t}\n```","state":0,"publishedAt":"2026-07-31T19:08:20+00:00","modifiedAt":"2026-07-31T19:26:50.767101+00:00","affected":[{"ecosystem":"npm","name":"jodit","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"4.12.31"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/xdan/jodit/security/advisories/GHSA-j839-gqq4-gf9j","https://github.com/xdan/jodit/commit/5fba6ef2381d151d7cb8e3c5ad0b9996af0f97b0","https://github.com/xdan/jodit","https://github.com/xdan/jodit/releases/tag/4.12.31"],"sourceUrl":"https://github.com/advisories/GHSA-j839-gqq4-gf9j","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-j839-gqq4-gf9j","identifiers":["GHSA-j839-gqq4-gf9j","CVE-2026-62324"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-10-26T08:22:31.5744378+00:00","complete":true,"records":1756,"lastAttemptAt":"2026-10-26T08:22:31.5744378+00:00","lastSuccessAt":"2026-10-26T08:22:31.7460185+00:00","error":null,"lastVerifiedAt":"2026-10-26T08:22:31.7460215+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-5ba1945314e042c2ced14b33f3680fa71692812d925544312b218f5eb8a26e71-7be668fdfdeaeb5567583afe06ddb6baaaf3c46d3179c97a67bbd53e37799306","complete":true,"records":414096,"lastAttemptAt":"2026-10-26T08:20:52.7258433+00:00","lastSuccessAt":"2026-10-26T08:20:52.6822499+00:00","error":null,"lastVerifiedAt":"2026-10-26T08:20:52.7258433+00:00","snapshotPublishedAt":"2026-10-26T07:44:21+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-43321610382942b8ad8c85917eab1546","complete":false,"records":4735,"lastAttemptAt":"2026-10-26T08:22:34.3793835+00:00","lastSuccessAt":"2026-10-24T23:18:49.862+00:00","error":"InvalidDataException","lastVerifiedAt":"2026-10-24T23:18:49.862+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":4764,"expectedIdentifiers":13474},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-10-26T08:22:31.5744378+00:00","complete":true,"records":1093,"lastAttemptAt":"2026-10-26T08:22:31.5744378+00:00","lastSuccessAt":"2026-10-26T08:22:31.7471397+00:00","error":null,"lastVerifiedAt":"2026-10-26T08:22:31.7471404+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-204a3fb93a00487e8f7f36db01dc851d","complete":true,"records":13413,"lastAttemptAt":"2026-10-26T08:22:32.3026766+00:00","lastSuccessAt":"2026-10-25T16:19:28.681+00:00","error":null,"lastVerifiedAt":"2026-10-25T16:19:28.681+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13474,"expectedIdentifiers":13474},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-2375a4e597279abf92e8747e140fe8da9d421877cd49cae3ac69a2cb78469b2c","complete":true,"records":9779,"lastAttemptAt":"2026-10-26T08:22:31.5153923+00:00","lastSuccessAt":"2026-10-24T13:19:53.1346702+00:00","error":null,"lastVerifiedAt":"2026-10-26T08:22:31.5153923+00:00","snapshotPublishedAt":"2026-10-24T03:03:12+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-16ad89dfea13ab9e9b0108e63a759d63bd4559cc68b58491107d62ee35bbbaf2","complete":true,"records":1965,"lastAttemptAt":"2026-10-26T08:20:52.7776084+00:00","lastSuccessAt":"2026-10-23T16:01:26.4487605+00:00","error":null,"lastVerifiedAt":"2026-10-26T08:20:52.7776084+00:00","snapshotPublishedAt":"2026-10-21T12:18:07+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-7da67a15aa6e56a0e3081a7646dc74d537e6ef974afc593c2b1794ed01970648","complete":true,"records":2969,"lastAttemptAt":"2026-10-26T08:22:31.531764+00:00","lastSuccessAt":"2026-10-24T13:19:59.1145547+00:00","error":null,"lastVerifiedAt":"2026-10-26T08:22:31.531764+00:00","snapshotPublishedAt":"2026-10-24T08:47:51+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-654e90fb44abcd729ddecf56d54c1b46ce87ca3181850e8bc65d5c8befceb659","complete":true,"records":230805,"lastAttemptAt":"2026-10-26T08:22:31.3932452+00:00","lastSuccessAt":"2026-10-26T08:22:30.1390577+00:00","error":null,"lastVerifiedAt":"2026-10-26T08:22:31.3932452+00:00","snapshotPublishedAt":"2026-10-26T07:48:28+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null}]}