diff --git a/src/lib/__tests__/articles-db-sanitization.test.ts b/src/lib/__tests__/articles-db-sanitization.test.ts
new file mode 100644
index 0000000..350e863
--- /dev/null
+++ b/src/lib/__tests__/articles-db-sanitization.test.ts
@@ -0,0 +1,93 @@
+import { describe, expect, it, vi } from 'vitest';
+
+vi.mock('../db/client', () => ({ db: {} }));
+vi.mock('../memories-db', () => ({ findMemoryByUrl: vi.fn(), createMemoryRecord: vi.fn() }));
+
+import { sanitizeArticlePayload } from '../articles-db';
+import { sanitizeBrowserMemorySnapshot } from '../browser-memory-import';
+
+const sourceUrl = 'https://source.example/articles/story?edition=1#intro';
+const sanitize = (content: string, url = sourceUrl) =>
+ sanitizeArticlePayload({ content, url, userId: 'synthetic-user' }).content;
+
+describe('article source URL sanitization', () => {
+ it.each([
+ ['/explore/', 'https://source.example/explore/'],
+ ['/insights/?topic=ai#latest', 'https://source.example/insights/?topic=ai#latest'],
+ ['next?edition=2#details', 'https://source.example/articles/next?edition=2#details'],
+ ['../archive/', 'https://source.example/archive/'],
+ ['?edition=2#details', 'https://source.example/articles/story?edition=2#details'],
+ ['//cdn.example/image.png', 'https://cdn.example/image.png'],
+ ])('resolves href and src %s using the full source URL', (relative, absolute) => {
+ const content = sanitize(`link`);
+ expect(content).toContain(`href="${absolute}"`);
+ expect(content).toContain(`src="${absolute}"`);
+ });
+
+ it('uses the source protocol and directory or trailing slash', () => {
+ expect(sanitize('
', 'http://source.example/')).toContain(
+ 'src="http://cdn.example/image.png"'
+ );
+ expect(sanitize('link', 'https://source.example/articles/')).toContain(
+ 'href="https://source.example/articles/next"'
+ );
+ });
+
+ it('keeps local fragments and safe markup unchanged', () => {
+ const html =
+ '
Text ' + + 'jumpexternal' + + 'email