security: sanitize episode HTML with DOMPurify (XSS)

Episode descriptions came from third-party RSS feeds and were rendered
via dangerouslySetInnerHTML without sanitization. Wrap with DOMPurify
using a whitelist of safe tags/attrs and an http(s)/mailto-only URI
regex.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Haiku 4.5 committed 2026-04-18 07:25:29 +02:00
1 parent e2adce33b8
commit 9bf95b4ac9
3 files changed
+6190 -4

No files matched your search

+6177
View File
File diff suppressed because it is too large. Load diff
+4 -2
View File
@@ -17,12 +17,14 @@
"@tanstack/react-query": "^5.1.0",
"zustand": "^4.4.1",
"@heroicons/react": "^2.0.18",
"axios": "^1.5.0",
"lucide-react": "^0.292.0"
"axios": "^1.7.9",
"lucide-react": "^0.292.0",
"dompurify": "^3.2.3"
},
"devDependencies": {
"@types/react": "^18.2.28",
"@types/react-dom": "^18.2.13",
"@types/dompurify": "^3.2.0",
"@vitejs/plugin-react": "^4.1.0",
"autoprefixer": "^10.4.16",
"eslint": "^9.18.0",
+9 -2
View File
@@ -1,5 +1,6 @@
import React from 'react';
import { createPortal } from 'react-dom';
import DOMPurify from 'dompurify';
import { X, Play, Clock, Calendar, BarChart2 } from 'lucide-react';
import { Episode } from '../services/episodeService';
@@ -109,9 +110,15 @@ export default function EpisodeDetails({ episode, isOpen, onClose, onPlay }: Epi
{/* Description */}
<div className="space-y-6">
<h3 className="text-xl font-display font-black">À propos de cet épisode</h3>
<div
<div
className="prose dark:prose-invert max-w-none text-[var(--text-secondary)] leading-relaxed text-lg"
dangerouslySetInnerHTML={{ __html: episode.description }}
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(episode.description || '', {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'b', 'i', 'u', 'a', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'blockquote', 'code', 'pre'],
ALLOWED_ATTR: ['href', 'target', 'rel'],
ALLOWED_URI_REGEXP: /^(?:https?|mailto):/i,
}),
}}
/>
</div>