const ALLOWED_TAGS = new Set([ "a", "b", "strong", "i", "em", "u", "p", "br", "hr", "ul", "ol", "li", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "pre", "code", "span", "div", "table", "thead", "tbody", "tr", "td", "th", "img", "sub", "sup", ]); const ALLOWED_ATTRS = new Set([ "href", "src", "alt", "title", "width", "height", "class", "id", "colspan", "rowspan", ]); function isSafeUrl(value: string): boolean { const trimmed = value.trim(); if (trimmed.startsWith("//")) return false; return /^(?:https?:\/\/|mailto:|tel:|\/|#)/i.test(trimmed); } /** Recursively walk a DOM node, keeping only allowed tags/attrs. */ function walkNode(node: Node, doc: Document): Node | null { if (node.nodeType === 3 /* TEXT */) { return doc.createTextNode(node.textContent ?? ""); } if (node.nodeType !== 1 /* ELEMENT */) return null; const el = node as Element; const tag = el.tagName.toLowerCase(); // Drop