From 69600ca74af6d881f0fcb859bd7c8c6a0dc00c05 Mon Sep 17 00:00:00 2001 From: nutty Date: Thu, 19 Mar 2026 23:01:56 +1100 Subject: [PATCH] Fixed JS injection exploit --- .common/utils/helpers.js | 60 ++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/.common/utils/helpers.js b/.common/utils/helpers.js index c89b721..40d508e 100644 --- a/.common/utils/helpers.js +++ b/.common/utils/helpers.js @@ -178,6 +178,14 @@ function DecodeHTMLString(html) { return txt.value; } +// Simple HTML escape function to prevent XSS attacks +function EscapeHTML(str) { + return str.replace(/[&<>"']/g, match => { + const escape = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }; + return escape[match]; + }); +} + function TranslateToFurry(sentence) { // Split on tags, keeping them in the result const parts = sentence.split(/(]*>)/gi); @@ -273,26 +281,26 @@ function RandomHex(str) { function ConstructMessageFromParts(parts) { return parts.map(part => { if (part.emoji) - return ` ${part.text} `; + return ` ${EscapeHTML(part.text)} `; if (!part.type) - return part.text; + return EscapeHTML(part.text); if (part.source == 'Twemoji') - return part.text; + return EscapeHTML(part.text); switch (part.type) { case "text": - return part.text; + return EscapeHTML(part.text); case "cheer": // Render the cheer emote image - const emoteImg = `${part.text}`; + const emoteImg = `${EscapeHTML(part.text)}`; // Render the bits count - const bitLabel = `${part.bits}`; + const bitLabel = `${EscapeHTML(part.bits.toString())}`; return emoteImg + bitLabel; default: - return `${part.text}`; + return `${EscapeHTML(part.text)}`; } }).join(''); } @@ -310,27 +318,43 @@ function RenderMessageWithEmotesHTML(originalMessage, emotes) { emotes.forEach(emote => { // Add text before the emote if (emote.startIndex > cursor) { - html += escapeHTML(originalMessage.slice(cursor, emote.startIndex)); + html += EscapeHTML(originalMessage.slice(cursor, emote.startIndex)); } // Add emote image - html += `${escapeHTML(emote.name)}`; + html += `${EscapeHTML(emote.name)}`; cursor = emote.endIndex + 1; }); // Add remaining text after last emote if (cursor < originalMessage.length) { - html += escapeHTML(originalMessage.slice(cursor)); - } - - // Simple HTML escape function - function escapeHTML(str) { - return str.replace(/[&<>"']/g, match => { - const escape = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }; - return escape[match]; - }); + html += EscapeHTML(originalMessage.slice(cursor)); } return html; +} + +async function LoadHTMLTemplate(name) { + const response = await fetch(name); + + if (!response.ok) { + throw new Error(`Failed to load template: ${name}`); + } + + const html = await response.text(); + + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = html; + + const template = tempDiv.querySelector("template"); + + if (!template) { + throw new Error(`No