Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 64 additions & 4 deletions src/lib/ircUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,24 +240,84 @@ export function renderMarkdown(
return `<a href="${sanitizedHref}"${titleAttr} target="_blank" rel="noopener noreferrer" class="${linkClass}">${text}</a>`;
};

// Strip all HTML tags from input before markdown processing
const textWithoutHtml = text.replace(/<[^>]*>/g, "");

marked.setOptions({
breaks: true,
gfm: true,
renderer: renderer,
});

// 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[^>]*>.*?<\/script>/gi, "")
.replace(/<iframe[^>]*>.*?<\/iframe>/gi, "")
.replace(/<object[^>]*>.*?<\/object>/gi, "")
.replace(/<embed[^>]*>.*?<\/embed>/gi, "")
.replace(/<style[^>]*>.*?<\/style>/gi, "")
.replace(/<link[^>]*\/?>/gi, "")
.replace(/<meta[^>]*\/?>/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 (
<div
Expand Down