security: sanitiser la sortie marked avec DOMPurify (XSS)

export-pdf-button et print-button injectaient marked(content) directement
dans innerHTML / document.write. Un lien Markdown javascript: passait le
filtre hasRestrictedChar et pouvait s'exécuter.

Ajout de DOMPurify.sanitize() via import dynamique (déjà présent en dep
transitive de jspdf) sur les deux composants, avec whitelist de tags
et d'attributs stricte. markdown-renderer n'est pas touché car
react-markdown-preview utilise rehype-sanitize en interne.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 21:48:26 +04:00
parent d8a63bc4d8
commit dc1f115bd6
3 changed files with 84 additions and 8 deletions
+41 -3
View File
@@ -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 <br>
@@ -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', '<br>')
+41 -3
View File
@@ -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', '<br>')
+2 -2
View File
@@ -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)