From d35c1072813ed1d9afbc6ce914503dd15860d361 Mon Sep 17 00:00:00 2001 From: Nicholas Santi Date: Sun, 12 Jul 2026 19:33:02 +0200 Subject: [PATCH 1/2] fix(editor): prevent links opening twice in read-only mode --- .../src/core/extensions/custom-link/helpers/clickHandler.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/editor/src/core/extensions/custom-link/helpers/clickHandler.ts b/packages/editor/src/core/extensions/custom-link/helpers/clickHandler.ts index 98c795cea3d..27e43b3f556 100644 --- a/packages/editor/src/core/extensions/custom-link/helpers/clickHandler.ts +++ b/packages/editor/src/core/extensions/custom-link/helpers/clickHandler.ts @@ -21,6 +21,10 @@ export function clickHandler(options: ClickHandlerOptions): Plugin { return false; } + if (!view.editable) { + return false; + } + let a = event.target as HTMLElement; const els: HTMLElement[] = []; From 295c843443beeb12ced8282a539a1f3dfca8cff3 Mon Sep 17 00:00:00 2001 From: Nicholas Santi Date: Sun, 12 Jul 2026 19:40:46 +0200 Subject: [PATCH 2/2] fix(editor): block unsafe read-only link navigation --- .../custom-link/helpers/clickHandler.ts | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/packages/editor/src/core/extensions/custom-link/helpers/clickHandler.ts b/packages/editor/src/core/extensions/custom-link/helpers/clickHandler.ts index 27e43b3f556..ce2033bc4b5 100644 --- a/packages/editor/src/core/extensions/custom-link/helpers/clickHandler.ts +++ b/packages/editor/src/core/extensions/custom-link/helpers/clickHandler.ts @@ -21,45 +21,39 @@ export function clickHandler(options: ClickHandlerOptions): Plugin { return false; } - if (!view.editable) { - return false; - } - - let a = event.target as HTMLElement; - const els: HTMLElement[] = []; + const eventTarget = event.target as HTMLElement | null; + const link = eventTarget?.closest?.("a") as HTMLLinkElement | null; - while (a?.nodeName !== "DIV") { - els.push(a); - a = a?.parentNode as HTMLElement; - } - - if (!els.find((value) => value.nodeName === "A")) { + if (!link || !view.dom.contains(link)) { return false; } const attrs = getAttributes(view.state, options.type.name); - const link = event.target as HTMLLinkElement; - - const href = link?.href ?? attrs.href; - const target = link?.target ?? attrs.target; - - if (link && href) { - // Defence-in-depth: link.href is the browser-resolved URL (whitespace - // already stripped by the browser's WHATWG URL parser), so a protocol - // check here is sufficient to catch any dangerous URI that slipped past - // the editor's parse/render-time guards. Matches the blocked-scheme list - // in isValidHttpUrl (javascript:, data:, vbscript:, file:, about:) - // to keep the policy consistent (GHSA-v2vv-7wq3-8w2j). - if (/^(javascript|data|vbscript|file|about):/i.test(href)) { - return false; - } + const href = link.href ?? attrs.href; + const target = link.target ?? attrs.target; - window.open(href, target); + if (!href) { + return false; + } + // Defence-in-depth: link.href is the browser-resolved URL (whitespace + // already stripped by the browser's WHATWG URL parser), so a protocol + // check here is sufficient to catch any dangerous URI that slipped past + // the editor's parse/render-time guards. Matches the blocked-scheme list + // in isValidHttpUrl (javascript:, data:, vbscript:, file:, about:) + // to keep the policy consistent (GHSA-v2vv-7wq3-8w2j). + if (/^(javascript|data|vbscript|file|about):/i.test(href)) { + event.preventDefault(); return true; } - return false; + if (!view.editable) { + return false; + } + + window.open(href, target); + + return true; }, }, });