From 145b3ea0a6ed26fe74d355f861db5604c54172f2 Mon Sep 17 00:00:00 2001 From: hugo Date: Sun, 22 Feb 2026 17:55:42 +0100 Subject: [PATCH] feat: added an URL sanitizer --- package-lock.json | 7 +++++++ package.json | 1 + src/main/shared/blogmark.ts | 8 +++++++- tests/engine/BlogmarkDeepLink.test.ts | 8 ++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 4e4122a..0263828 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@braintree/sanitize-url": "^7.1.2", "@floating-ui/dom": "^1.7.5", "@highlightjs/cdn-assets": "^11.11.1", "@libsql/client": "^0.17.0", @@ -447,6 +448,12 @@ "node": ">=18" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT" + }, "node_modules/@codemirror/autocomplete": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", diff --git a/package.json b/package.json index 1d5a691..e755127 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "wait-on": "^9.0.3" }, "dependencies": { + "@braintree/sanitize-url": "^7.1.2", "@floating-ui/dom": "^1.7.5", "@highlightjs/cdn-assets": "^11.11.1", "@libsql/client": "^0.17.0", diff --git a/src/main/shared/blogmark.ts b/src/main/shared/blogmark.ts index c040b14..060568b 100644 --- a/src/main/shared/blogmark.ts +++ b/src/main/shared/blogmark.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { sanitizeUrl } from '@braintree/sanitize-url'; import { normalizeNonEmptyTaxonomyTerm } from '../engine/taxonomyUtils'; const MAX_TITLE_LENGTH = 200; @@ -42,9 +43,14 @@ function sanitizeHttpUrl(rawUrl: unknown): string | null { return null; } + const policySanitized = sanitizeUrl(trimmed); + if (policySanitized === 'about:blank') { + return null; + } + let parsed: URL; try { - parsed = new URL(trimmed); + parsed = new URL(policySanitized); } catch { return null; } diff --git a/tests/engine/BlogmarkDeepLink.test.ts b/tests/engine/BlogmarkDeepLink.test.ts index 48a10a3..f3155b5 100644 --- a/tests/engine/BlogmarkDeepLink.test.ts +++ b/tests/engine/BlogmarkDeepLink.test.ts @@ -25,6 +25,14 @@ describe('blogmark deep-link payload', () => { expect(payload).toBeNull(); }); + it('rejects entity-obfuscated script URLs', () => { + const payload = extractBlogmarkPayloadFromDeepLink( + 'bds://new-post?title=Unsafe&url=javascript%26%2397%3Balert(1)', + ); + + expect(payload).toBeNull(); + }); + it('builds safe markdown source link', () => { const markdown = buildBlogmarkMarkdownLink('A [title] (test)', 'https://example.com/x?y=1'); expect(markdown).toBe('[A \\[title\\] \\(test\\)]()');