diff --git a/src/lib/components/chat/Messages/Citations/CitationModal.svelte b/src/lib/components/chat/Messages/Citations/CitationModal.svelte
index 57ea654364..a56e409b27 100644
--- a/src/lib/components/chat/Messages/Citations/CitationModal.svelte
+++ b/src/lib/components/chat/Messages/Citations/CitationModal.svelte
@@ -6,6 +6,7 @@
import { WEBUI_API_BASE_URL } from '$lib/constants';
import { settings, config } from '$lib/stores';
import { injectCsp } from '$lib/utils/csp';
+ import { isValidHttpUrl } from '$lib/utils';
import XMark from '$lib/components/icons/XMark.svelte';
import Textarea from '$lib/components/common/Textarea.svelte';
@@ -71,7 +72,7 @@
const baseUrl = file_id
? `${WEBUI_API_BASE_URL}/files/${file_id}/content${page !== undefined ? `#page=${page + 1}` : ''}`
- : sourceUrl?.includes('http')
+ : isValidHttpUrl(sourceUrl)
? sourceUrl
: null;
@@ -101,10 +102,10 @@
{#if citation?.source?.name}
{@const document = mergedDocuments?.[0]}
- {#if document?.metadata?.file_id || document.source?.url?.includes('http')}
+ {#if document?.metadata?.file_id || isValidHttpUrl(document.source?.url)}
- {#if document.source?.url?.includes('http')}
+ {#if isValidHttpUrl(document.source?.url)}
{@const snippetUrl = getTextFragmentUrl(document)}
{#if snippetUrl}
{:else if token.type === 'link'}
{@const noteId = getNoteIdFromHref(token.href)}
+ {@const safeHref = safeLinkUrl(token.href)}
{#if noteId}
{:else if token.tokens}
{:else}
+ import { safeLinkUrl } from '$lib/utils';
+
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
import Search from '$lib/components/icons/Search.svelte';
@@ -59,7 +61,7 @@
{#if status?.items}
{#each status.items as item, itemIdx}
@@ -99,7 +101,7 @@
{:else if status?.urls}
{#each status.urls as url, urlIdx}
diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts
index 91b66f8524..7073503aa9 100644
--- a/src/lib/utils/index.ts
+++ b/src/lib/utils/index.ts
@@ -981,6 +981,20 @@ export const isValidHttpUrl = (string: string) => {
return url.protocol === 'http:' || url.protocol === 'https:';
};
+const SAFE_LINK_PROTOCOLS = ['http:', 'https:', 'mailto:', 'tel:'];
+
+export const safeLinkUrl = (url: string): string | undefined => {
+ let protocol;
+ try {
+ protocol = new URL(url).protocol;
+ } catch (_) {
+ // No scheme to parse, so the browser resolves it against our own origin.
+ return url;
+ }
+
+ return SAFE_LINK_PROTOCOLS.includes(protocol) ? url : undefined;
+};
+
export const isYoutubeUrl = (url: string) => {
return (
url.startsWith('https://www.youtube.com') ||