diff --git a/src/lib/components/views/PlaylistDetailView.svelte b/src/lib/components/views/PlaylistDetailView.svelte
index ac3434aee..fd0364c42 100644
--- a/src/lib/components/views/PlaylistDetailView.svelte
+++ b/src/lib/components/views/PlaylistDetailView.svelte
@@ -22,6 +22,7 @@
import { isTrackUnavailable, clearTrackUnavailable, subscribe as subscribeUnavailable } from '$lib/stores/unavailableTracksStore';
import { isBlacklisted as isArtistBlacklisted } from '$lib/stores/artistBlacklistStore';
import { showToast } from '$lib/stores/toastStore';
+ import { sanitizeHtml } from '$lib/utils/sanitize';
import { get } from 'svelte/store';
import { t } from '$lib/i18n';
import { onMount, tick } from 'svelte';
@@ -2020,7 +2021,7 @@
Playlist
{playlist.name}
{#if playlist.description}
- {playlist.description}
+ {@html sanitizeHtml(playlist.description)}
{/if}
{playlist.owner.name}
@@ -2603,6 +2604,15 @@
line-height: 1.4;
}
+ .playlist-description :global(a) {
+ color: var(--accent-primary);
+ text-decoration: none;
+ }
+
+ .playlist-description :global(a:hover) {
+ text-decoration: underline;
+ }
+
.playlist-info {
display: flex;
align-items: center;
diff --git a/src/lib/utils/sanitize.ts b/src/lib/utils/sanitize.ts
new file mode 100644
index 000000000..b73e4b351
--- /dev/null
+++ b/src/lib/utils/sanitize.ts
@@ -0,0 +1,37 @@
+/**
+ * Lightweight HTML sanitizer for rendering API content (e.g. Qobuz playlist descriptions).
+ * Allows safe tags and strips dangerous elements/attributes.
+ */
+
+const ALLOWED_TAGS = new Set([
+ 'b', 'i', 'em', 'strong', 'br', 'a', 'p', 'span', 'ul', 'ol', 'li',
+]);
+
+/** Strip event handler attributes and javascript: URLs */
+function stripDangerousAttrs(tag: string): string {
+ // Remove on* event attributes
+ tag = tag.replace(/\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]*)/gi, '');
+ // Remove javascript: URLs from href/src
+ tag = tag.replace(/(href|src)\s*=\s*(?:"javascript:[^"]*"|'javascript:[^']*')/gi, '$1=""');
+ return tag;
+}
+
+export function sanitizeHtml(html: string): string {
+ if (!html) return '';
+
+ // Remove