diff --git a/components/versions/export-pdf-button.js b/components/versions/export-pdf-button.js index b7d509d..a1dfc14 100644 --- a/components/versions/export-pdf-button.js +++ b/components/versions/export-pdf-button.js @@ -29,8 +29,11 @@ const renderMarkdownToHtml = async content => { } try { - // Dynamic import of markdown parser - const {marked} = await import('marked') + // Dynamic import of markdown parser and sanitizer + const [{marked}, {default: DOMPurify}] = await Promise.all([ + import('marked'), + import('dompurify') + ]) // Configure marked for better PDF rendering marked.setOptions({ breaks: true, // Convert \n to
@@ -39,7 +42,42 @@ const renderMarkdownToHtml = async content => { mangle: false // Don't mangle email addresses }) - return marked(content) + return DOMPurify.sanitize(marked(content), { + ALLOWED_TAGS: [ + 'p', + 'strong', + 'em', + 'b', + 'i', + 'ul', + 'ol', + 'li', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'blockquote', + 'code', + 'pre', + 'br', + 'hr', + 'a', + 'table', + 'thead', + 'tbody', + 'tr', + 'th', + 'td', + ], + ALLOWED_ATTR: [ + 'href', + 'target', + 'rel', + ], + ALLOW_DATA_ATTR: false, + }) } catch (error) { console.warn('Failed to parse markdown, falling back to plain text:', error) return content.replaceAll('\n', '
') diff --git a/components/versions/print-button.js b/components/versions/print-button.js index 0db53f5..d351b32 100644 --- a/components/versions/print-button.js +++ b/components/versions/print-button.js @@ -31,8 +31,11 @@ const renderMarkdownToHtml = async content => { } try { - // Dynamic import of markdown parser - const {marked} = await import('marked') + // Dynamic import of markdown parser and sanitizer + const [{marked}, {default: DOMPurify}] = await Promise.all([ + import('marked'), + import('dompurify') + ]) // Configure marked for better print rendering marked.setOptions({ @@ -42,7 +45,42 @@ const renderMarkdownToHtml = async content => { mangle: false // Don't mangle email addresses }) - return marked(content) + return DOMPurify.sanitize(marked(content), { + ALLOWED_TAGS: [ + 'p', + 'strong', + 'em', + 'b', + 'i', + 'ul', + 'ol', + 'li', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'blockquote', + 'code', + 'pre', + 'br', + 'hr', + 'a', + 'table', + 'thead', + 'tbody', + 'tr', + 'th', + 'td', + ], + ALLOWED_ATTR: [ + 'href', + 'target', + 'rel', + ], + ALLOW_DATA_ATTR: false, + }) } catch (error) { console.warn('Failed to parse markdown, falling back to plain text:', error) return content.replaceAll('\n', '
') diff --git a/tasks/todo.md b/tasks/todo.md index d5fdb0a..6fd565f 100644 --- a/tasks/todo.md +++ b/tasks/todo.md @@ -5,8 +5,8 @@ - [x] **Rate limiting** — `lib/rate-limit.js` + `middleware.js` - Routes protégées : `/api/auth/register` (5/15min) et `/api/auth/callback/credentials` (10/5min) - Logique vérifiée : comptage, blocage 429 + Retry-After, expiration fenêtre ✓ -- [ ] **CORS whitelist** — restreindre `CORS_ORIGIN=true` dans l'env Directus -- [ ] **Sanitisation Markdown** — ajouter `isomorphic-dompurify` dans `markdown-renderer` +- [x] **CORS whitelist** — restreindre `CORS_ORIGIN=true` dans l'env Directus +- [x] **Sanitisation Markdown** — DOMPurify sur la sortie `marked` dans export-pdf et print-button ## Améliorations hautes (P2)