feat: added an URL sanitizer

This commit is contained in:
2026-02-22 17:55:42 +01:00
parent 509afa4c85
commit 145b3ea0a6
4 changed files with 23 additions and 1 deletions

7
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^7.1.2",
"@floating-ui/dom": "^1.7.5", "@floating-ui/dom": "^1.7.5",
"@highlightjs/cdn-assets": "^11.11.1", "@highlightjs/cdn-assets": "^11.11.1",
"@libsql/client": "^0.17.0", "@libsql/client": "^0.17.0",
@@ -447,6 +448,12 @@
"node": ">=18" "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": { "node_modules/@codemirror/autocomplete": {
"version": "6.20.0", "version": "6.20.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz",

View File

@@ -60,6 +60,7 @@
"wait-on": "^9.0.3" "wait-on": "^9.0.3"
}, },
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^7.1.2",
"@floating-ui/dom": "^1.7.5", "@floating-ui/dom": "^1.7.5",
"@highlightjs/cdn-assets": "^11.11.1", "@highlightjs/cdn-assets": "^11.11.1",
"@libsql/client": "^0.17.0", "@libsql/client": "^0.17.0",

View File

@@ -1,4 +1,5 @@
import { z } from 'zod'; import { z } from 'zod';
import { sanitizeUrl } from '@braintree/sanitize-url';
import { normalizeNonEmptyTaxonomyTerm } from '../engine/taxonomyUtils'; import { normalizeNonEmptyTaxonomyTerm } from '../engine/taxonomyUtils';
const MAX_TITLE_LENGTH = 200; const MAX_TITLE_LENGTH = 200;
@@ -42,9 +43,14 @@ function sanitizeHttpUrl(rawUrl: unknown): string | null {
return null; return null;
} }
const policySanitized = sanitizeUrl(trimmed);
if (policySanitized === 'about:blank') {
return null;
}
let parsed: URL; let parsed: URL;
try { try {
parsed = new URL(trimmed); parsed = new URL(policySanitized);
} catch { } catch {
return null; return null;
} }

View File

@@ -25,6 +25,14 @@ describe('blogmark deep-link payload', () => {
expect(payload).toBeNull(); 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', () => { it('builds safe markdown source link', () => {
const markdown = buildBlogmarkMarkdownLink('A [title] (test)', 'https://example.com/x?y=1'); const markdown = buildBlogmarkMarkdownLink('A [title] (test)', 'https://example.com/x?y=1');
expect(markdown).toBe('[A \\[title\\] \\(test\\)](<https://example.com/x?y=1>)'); expect(markdown).toBe('[A \\[title\\] \\(test\\)](<https://example.com/x?y=1>)');