From 121c351416f2b67bb8c3c6b38105180b10a648b1 Mon Sep 17 00:00:00 2001 From: nutty Date: Thu, 27 Aug 2026 15:09:34 +1000 Subject: [PATCH] Sanitized custom message fields --- .common/utils/helpers.js | 53 ++++++++++++++++++++++++++++++++++ printer-bot/contents/script.js | 16 +++++----- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/.common/utils/helpers.js b/.common/utils/helpers.js index d7c4b99..77d6ca5 100644 --- a/.common/utils/helpers.js +++ b/.common/utils/helpers.js @@ -709,4 +709,57 @@ function VersionCheck(requiredVersion, installedVersion) { // 4. PERFECT: Installed version meets or exceeds the required baseline. console.log(`VersionCheck: Installed version meets or exceeds the required baseline. Required: ${requiredVersion}, Installed: ${installedVersion}`); return 'compatible'; +} + +function SanitizeHTML(htmlString) { + // 1. Parse the string into a temporary DOM document + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlString, 'text/html'); + + // 2. Define allowed tags and attributes + const allowedTags = ['IMG', 'SPAN', 'B', 'I', 'BR', 'EM', 'STRONG']; + const allowedAttrs = ['src', 'class']; + + // 3. Walk through all elements in the parsed document + const allElements = doc.body.querySelectorAll('*'); + allElements.forEach(el => { + // Check if it's an tag and enforce class restrictions + if (el.tagName === 'IMG') { + const className = el.getAttribute('class') || ''; + // Split classes by whitespace in case there are multiple (e.g., class="emote custom") + const classes = className.split(/\s+/); + + // Must contain either 'emote' or 'bits' to be allowed + const hasValidClass = classes.includes('emote') || classes.includes('bits'); + + if (!hasValidClass) { + // Unwrap or remove the unauthorized tag + const parent = el.parentNode; + while (el.firstChild) { + parent.insertBefore(el.firstChild, el); + } + parent.removeChild(el); + return; + } + } + + // If any other tag isn't in our allowed list, unwrap it + if (!allowedTags.includes(el.tagName)) { + const parent = el.parentNode; + while (el.firstChild) { + parent.insertBefore(el.firstChild, el); + } + parent.removeChild(el); + return; + } + + // Strip out any attributes not explicitly allowed (like onerror, onload, etc.) + Array.from(el.attributes).forEach(attr => { + if (!allowedAttrs.includes(attr.name.toLowerCase())) { + el.removeAttribute(attr.name); + } + }); + }); + + return doc.body.innerHTML; } \ No newline at end of file diff --git a/printer-bot/contents/script.js b/printer-bot/contents/script.js index 5fa6563..b705cfb 100644 --- a/printer-bot/contents/script.js +++ b/printer-bot/contents/script.js @@ -51,7 +51,7 @@ async function CustomEvent(data) { subtitleEl.innerText = `${data.user}`; const messageEl = document.createElement('div'); - messageEl.innerHTML = data.message; + messageEl.innerHTML = SanitizeHTML(data.message); // Render emotes for (i in data.emotes) { @@ -116,7 +116,7 @@ async function CustomEvent(data) { const messageEl = document.createElement('div'); messageEl.innerHTML = `${data.cumulative} months${data.monthStreak > 1 ? ' (' + data.monthStreak + ' in a row!)' : ''}`; if (data.messageStripped) - messageEl.innerHTML += `

${data.messageStripped}`; + messageEl.innerHTML += `

${SanitizeHTML(data.messageStripped)}`; contentEl.appendChild(messageEl); @@ -196,7 +196,7 @@ async function CustomEvent(data) { subtitleEl.innerText = `${data.user}`; const messageEl = document.createElement('div'); - messageEl.innerHTML = data.rawInput; + messageEl.innerHTML = SanitizeHTML(data.rawInput); // // Render emotes // for (i in data.emotes) { @@ -284,7 +284,7 @@ async function CustomEvent(data) { const messageEl = document.createElement('div'); messageEl.innerHTML = `${data.user}
sent a Super Chat!`; if (data.message) - messageEl.innerHTML += `

${data.message}`; + messageEl.innerHTML += `

${SanitizeHTML(data.message)}`; contentEl.appendChild(messageEl); @@ -424,7 +424,7 @@ async function CustomEvent(data) { if (data.tipMessage) { const messageEl = document.createElement('div'); - messageEl.innerHTML = `${data.tipMessage}`; + messageEl.innerHTML = `${SanitizeHTML(data.tipMessage)}`; contentEl.appendChild(messageEl); } @@ -448,7 +448,7 @@ async function CustomEvent(data) { if (data.donationMessage) { const messageEl = document.createElement('div'); - messageEl.innerHTML = `${data.donationMessage}`; + messageEl.innerHTML = `${SanitizeHTML(data.donationMessage)}`; contentEl.appendChild(messageEl); } @@ -475,7 +475,7 @@ async function CustomEvent(data) { if (data["fw.message"]) { const messageEl = document.createElement('div'); - messageEl.innerHTML = `${data["fw.message"]}`; + messageEl.innerHTML = `${SanitizeHTML(data["fw.message"])}`; contentEl.appendChild(messageEl); } @@ -537,7 +537,7 @@ async function CustomEvent(data) { if (customMessage) { const txt = document.createElement("textarea"); txt.innerHTML = customMessage; - customMessageEl.innerHTML += `
${txt.value}`; + customMessageEl.innerHTML += `
${SanitizeHTML(txt.value)}`; } // Add a cute thank you message because you're uwu like that