diff --git a/src/lib/documents/markdown.ts b/src/lib/documents/markdown.ts index 2bbc7fd..982fcec 100644 --- a/src/lib/documents/markdown.ts +++ b/src/lib/documents/markdown.ts @@ -468,7 +468,7 @@ export function markdownToDoc(markdown: string): JSONContent { // Table: header row, | --- | separator, then body rows. if (/^\|.*\|\s*$/.test(line) && i + 1 < lines.length && /^\|[\s:|-]+\|\s*$/.test(lines[i + 1])) { flushParagraph(); - const parseRow = (row: string, header: boolean): JSONContent => ({ + const parseRow = (row: string, header: boolean): JSONContent => ({ type: 'tableRow', content: row .replace(/^\|/, '') diff --git a/src/lib/documents/sanitizeHtml.ts b/src/lib/documents/sanitizeHtml.ts index ec1d37e..484f24c 100644 --- a/src/lib/documents/sanitizeHtml.ts +++ b/src/lib/documents/sanitizeHtml.ts @@ -42,6 +42,13 @@ const ALLOWED_ELEMENTS = new Set([ /** class="task-list" / "task-item" and data-checked carry checklist state. */ const ALLOWED_CLASSES = new Set(['task-list', 'task-item']); +/** + * Elements whose entire subtree must be dropped rather than unwrapped: their + * text content is not safe to surface (e.g. `` would + * otherwise become a literal "alert(1)" text node). + */ +const REMOVE_ENTIRELY = new Set(['script', 'style']); + const SAFE_PROTOCOLS = new Set(['https:', 'http:', 'mailto:', 'nostr:']); function sanitizeHref(value: string | null): string | null { @@ -94,12 +101,19 @@ function sanitizeAttributes(element: Element): void { } function sanitizeElement(element: Element): void { + const tag = element.tagName.toLowerCase(); + if (REMOVE_ENTIRELY.has(tag)) { + // Drop the element and all of its content — unwrapping would surface + // script/style text as a literal text node. + element.remove(); + return; + } + // Children first: a disallowed child is unwrapped before we look up again. for (const child of [...element.children]) { sanitizeElement(child); } - const tag = element.tagName.toLowerCase(); if (!ALLOWED_ELEMENTS.has(tag)) { // Unwrap: keep the (already sanitized) children, drop the wrapper. This // keeps the text of a