// Markdown → sanitized HTML, ported from app.js's `mdNode`. Message // bodies rendered into the live stream (assistant text, send/ask/answer/ // recv payloads) are untrusted (peer-agent / matrix-relayed content, // agent-authored files) — `marked` itself no longer sanitizes (v5+ // dropped the built-in sanitizer), so every parse is run through // DOMPurify before it's ever handed to `dangerouslySetInnerHTML`. import { marked } from 'marked'; import DOMPurify from 'dompurify'; marked.setOptions({ breaks: true, gfm: true }); const ESCAPE_RE = /[&<>"]/g; const ESCAPE_MAP: Record = { '&': '&', '<': '<', '>': '>', '"': '"' }; /** Render `text` as sanitized markdown HTML. Falls back to escaped plain * text if `marked` throws (mirrors app.js's try/catch fallback). */ export function renderMarkdown(text: string | null | undefined): string { const src = String(text ?? ''); try { return DOMPurify.sanitize(marked.parse(src) as string); } catch (err) { console.warn('marked failed', err); return src.replace(ESCAPE_RE, (c) => ESCAPE_MAP[c] ?? c); } }