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)