From 7405f5dc4bb8aa629bf97b00dc5dae1ea50d355e Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 30 May 2026 06:27:43 -0700 Subject: [PATCH] fix: ignore malformed markdown file links (#3695) --- .../editor/markdown-internal-links.test.ts | 4 ++++ .../src/components/editor/markdown-internal-links.ts | 12 ++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/components/editor/markdown-internal-links.test.ts b/src/renderer/src/components/editor/markdown-internal-links.test.ts index 95e8e6407f5..8d624c1430f 100644 --- a/src/renderer/src/components/editor/markdown-internal-links.test.ts +++ b/src/renderer/src/components/editor/markdown-internal-links.test.ts @@ -123,6 +123,10 @@ describe('resolveMarkdownLinkTarget', () => { }) }) + it('returns null for malformed percent-encoded file URL paths', () => { + expect(resolveMarkdownLinkTarget('file:///repo/docs/%zz.md', SOURCE, ROOT)).toBeNull() + }) + it('returns null for empty href', () => { expect(resolveMarkdownLinkTarget('', SOURCE, ROOT)).toBeNull() expect(resolveMarkdownLinkTarget(undefined, SOURCE, ROOT)).toBeNull() diff --git a/src/renderer/src/components/editor/markdown-internal-links.ts b/src/renderer/src/components/editor/markdown-internal-links.ts index deac13b4c50..09694c86942 100644 --- a/src/renderer/src/components/editor/markdown-internal-links.ts +++ b/src/renderer/src/components/editor/markdown-internal-links.ts @@ -51,8 +51,13 @@ function toFileUrl(filePath: string): string { return `file:///${segments.join('/')}` } -function fileUrlToAbsolutePath(url: URL): string { - let absolutePath = decodeURIComponent(url.pathname) +function fileUrlToAbsolutePath(url: URL): string | null { + let absolutePath: string + try { + absolutePath = decodeURIComponent(url.pathname) + } catch { + return null + } // Windows: "/C:/foo" → "C:/foo" if (/^\/[A-Za-z]:\//.test(absolutePath)) { absolutePath = absolutePath.slice(1) @@ -175,6 +180,9 @@ export function resolveMarkdownLinkTarget( } const rawAbsolutePath = fileUrlToAbsolutePath(resolved) + if (rawAbsolutePath === null) { + return null + } // Why: hash-based line anchor takes precedence; fall back to trailing // `:line:col` syntax only if no hash anchor was found.