diff --git a/src/lib/ircUtils.tsx b/src/lib/ircUtils.tsx index ba606063..a45fabec 100644 --- a/src/lib/ircUtils.tsx +++ b/src/lib/ircUtils.tsx @@ -240,6 +240,9 @@ export function renderMarkdown( return `${text}`; }; + // Strip all HTML tags from input before markdown processing + const textWithoutHtml = text.replace(/<[^>]*>/g, ""); + marked.setOptions({ breaks: true, gfm: true, @@ -247,17 +250,74 @@ export function renderMarkdown( }); // Parse markdown to HTML - const html = marked.parse(text) as string; - - // Additional security: remove any remaining script tags or dangerous content that might have slipped through - const sanitizedHtml = html + const html = marked.parse(textWithoutHtml) as string; + + // Additional security: only allow specific markdown-related HTML tags + // Define allowed HTML tags for markdown rendering + const allowedTags = new Set([ + "p", + "br", + "strong", + "b", + "em", + "i", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "ul", + "ol", + "li", + "blockquote", + "code", + "pre", + "a", + "img", + "hr", + "table", + "thead", + "tbody", + "tr", + "th", + "td", + "del", + "ins", + ]); + + // Remove dangerous content first + let sanitizedHtml = html .replace(/]*>.*?<\/script>/gi, "") .replace(/]*>.*?<\/iframe>/gi, "") .replace(/]*>.*?<\/object>/gi, "") .replace(/]*>.*?<\/embed>/gi, "") + .replace(/]*>.*?<\/style>/gi, "") + .replace(/]*\/?>/gi, "") + .replace(/]*\/?>/gi, "") .replace(/on\w+="[^"]*"/gi, "") // Remove event handlers .replace(/javascript:/gi, "#"); + // Remove any HTML tags that are not in the allowed list + sanitizedHtml = sanitizedHtml.replace( + /<\/?([a-zA-Z][a-zA-Z0-9]*)(?:\s[^>]*)?>/g, + (match, tagName) => { + return allowedTags.has(tagName.toLowerCase()) ? match : ""; + }, + ); + + // Remove all style attributes from allowed tags and ensure img tags have controlled styling + sanitizedHtml = sanitizedHtml.replace(/<([^>]+)>/g, (match, content) => { + // For img tags, ensure they have our controlled style + if (content.trim().startsWith("img")) { + // Remove any existing style and add our controlled one + const withoutStyle = content.replace(/\s+style="[^"]*"/gi, ""); + return `<${withoutStyle} style="max-height: 150px;">`; + } + // Remove style attributes from all other tags + return `<${content.replace(/\s+style="[^"]*"/gi, "")}>`; + }); + // Return a div with dangerouslySetInnerHTML return (