From 78adb65f60fba06b66be02234c40c4492c20fca9 Mon Sep 17 00:00:00 2001 From: Zack Rauen Date: Thu, 13 Apr 2023 14:35:28 -0400 Subject: [PATCH 1/2] Initial CSP whitelist --- injector/src/modules/csp.js | 79 +++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/injector/src/modules/csp.js b/injector/src/modules/csp.js index 16aab5e4..6fc06640 100644 --- a/injector/src/modules/csp.js +++ b/injector/src/modules/csp.js @@ -1,14 +1,85 @@ import electron from "electron"; +const whitelist = { + // Discord includes unsafe-inline already + script: [ + "https://*.github.io", + "https://cdnjs.cloudflare.com" // Used for Monaco + ], + + // Discord includes nothing we need + connect: [ + "https://api.github.com", + ], + + // Discord includes unsafe-inline already + style: [ + "https://*.github.io", + "https://cdnjs.cloudflare.com", // Used for Monaco + "https://fonts.googleapis.com", + ], + + // Discord includes the other google font url + font: [ + "data:", + "https://*.github.io", + "https://cdnjs.cloudflare.com", + "https://fonts.googleapis.com", + ], + + // Discord includes several sources already including imgur + img: [ + "https://*.github.io", + "https://ik.imagekit.io", + "https://source.unsplash.com", + ], + + // Discord does not include this normally + worker: [ + "'self'", // To allow Discord's own workers + "data:", // Used for Monaco + ], + +}; + +const types = Object.keys(whitelist); + +const strings = {}; +for (const key of types) strings[key] = whitelist[key].join(" ") + " "; + + +function addToCSP(csp, type) { + // If it's in the middle of the policy (most common case) + if (csp.includes(`; ${type}-src `)) return csp.replace(`; ${type}-src `, `; ${type}-src ${strings[type]}`); + + // If it's the first rule (very uncommon, default-src should be first) + else if (csp.includes(`${type}-src `)) return csp.replace(`${type}-src `, `${type}-src ${strings[type]}`); + + // Otherwise, rule doesn't exist, add it to the end + return csp = csp + `; ${type}-src ${strings[type]}; `; +} + export default class { static remove() { electron.session.defaultSession.webRequest.onHeadersReceived(function(details, callback) { - const headers = Object.keys(details.responseHeaders); - for (let h = 0; h < headers.length; h++) { - const key = headers[h]; + const headerKeys = Object.keys(details.responseHeaders); + for (let h = 0; h < headerKeys.length; h++) { + const key = headerKeys[h]; + + // Because the casing is inconsistent for whatever reason... if (key.toLowerCase().indexOf("content-security-policy") !== 0) continue; - delete details.responseHeaders[key]; + + // Grab current CSP policy and make sure it's Discord's + // since that's the only one we need to modify + let csp = details.responseHeaders[key]; + if (Array.isArray(csp)) csp = csp[0]; + if (!csp.toLowerCase().includes("discordapp")) continue; + + // Iterate over all whitelisted types and update the header + for (let k = 0; k < types.length; k++) csp = addToCSP(csp, types[k]); + details.responseHeaders[key] = [csp]; } + callback({cancel: false, responseHeaders: details.responseHeaders}); }); } From 5717153ed057f35b0685b4c1e87f4a36deefc233 Mon Sep 17 00:00:00 2001 From: Zack Rauen Date: Fri, 14 Apr 2023 15:00:22 -0400 Subject: [PATCH 2/2] Update based on themes --- injector/src/modules/csp.js | 34 +++++++++++++++---- renderer/src/modules/updater.js | 60 ++++++++++++++++++++------------- 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/injector/src/modules/csp.js b/injector/src/modules/csp.js index 6fc06640..e3c4b63d 100644 --- a/injector/src/modules/csp.js +++ b/injector/src/modules/csp.js @@ -7,31 +7,51 @@ const whitelist = { "https://cdnjs.cloudflare.com" // Used for Monaco ], - // Discord includes nothing we need - connect: [ - "https://api.github.com", - ], - // Discord includes unsafe-inline already style: [ "https://*.github.io", "https://cdnjs.cloudflare.com", // Used for Monaco "https://fonts.googleapis.com", + "https://fonts.cdnfonts.com", + "https://cdn.statically.io", + "https://rawgit.com", + "https://raw.githack.com", + "https://rsms.me", + "https://cdn.jsdelivr.net", ], - // Discord includes the other google font url + // Discord includes fonts.gstatic.com font: [ "data:", "https://*.github.io", "https://cdnjs.cloudflare.com", "https://fonts.googleapis.com", + "https://raw.githack.com", + "https://cdn.jsdelivr.net", ], - // Discord includes several sources already including imgur + // Discord includes several sources already including imgur and data: img: [ "https://*.github.io", "https://ik.imagekit.io", "https://source.unsplash.com", + "https://raw.githubusercontent.com", + "https://svgur.com", + "https://i.ibb.co", + "https://rawgit.com", + "https://bowmanfox.xyz", + "https://paz.pw", + "https://adx74.fr", + "https://media.tenor.com", // included by discord already + "https://upload.wikimedia.org", + "https://svgrepo.com", + "https://ch3rry.red", + "https://teamcofh.com", + "https://icon-library.net", + "https://images.pexels.com", + "https://user-images.githubusercontent.com", + "https://emoji.gg", + "https://cdn-icons-png.flaticon.com", ], // Discord does not include this normally diff --git a/renderer/src/modules/updater.js b/renderer/src/modules/updater.js index b5e3b12b..2041be68 100644 --- a/renderer/src/modules/updater.js +++ b/renderer/src/modules/updater.js @@ -76,31 +76,45 @@ export class CoreUpdater { } static async checkForUpdate(showNotice = true) { - const resp = await fetch(`https://api.github.com/repos/BetterDiscord/BetterDiscord/releases/latest`,{ - method: "GET", - headers: { - "Accept": "application/json", - "Content-Type": "application/json", - "User-Agent": "BetterDiscord Updater" - } - }); + try { + const buffer = await new Promise((resolve, reject) => { + request({ + url: "https://api.github.com/repos/BetterDiscord/BetterDiscord/releases/latest", + method: "get", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "BetterDiscord Updater" + } + }, (err, resp, body) => { + if (err || resp.statusCode != 200) return reject(err || `${resp.statusCode} ${resp.statusMessage}`); + return resolve(body); + }); + }); - const data = await resp.json(); - this.apiData = data; - const remoteVersion = data.tag_name.startsWith("v") ? data.tag_name.slice(1) : data.tag_name; - this.hasUpdate = remoteVersion > Config.version; - this.remoteVersion = remoteVersion; - if (!this.hasUpdate || !showNotice) return; + const data = JSON.parse(buffer.toString()); + this.apiData = data; + const remoteVersion = data.tag_name.startsWith("v") ? data.tag_name.slice(1) : data.tag_name; + this.hasUpdate = remoteVersion > Config.version; + this.remoteVersion = remoteVersion; + if (!this.hasUpdate || !showNotice) return; - const close = Notices.info(Strings.Updater.updateAvailable.format({version: remoteVersion}), { - buttons: [{ - label: Strings.Notices.moreInfo, - onClick: () => { - close(); - UserSettingsWindow?.open?.("updates"); - } - }] - }); + const close = Notices.info(Strings.Updater.updateAvailable.format({version: remoteVersion}), { + buttons: [{ + label: Strings.Notices.moreInfo, + onClick: () => { + close(); + UserSettingsWindow?.open?.("updates"); + } + }] + }); + } + catch (err) { + Logger.stacktrace("Updater", "Failed to check update", err); + Modals.showConfirmationModal(Strings.Updater.updateFailed, Strings.Updater.updateFailedMessage, { + cancelText: null + }); + } } static async update() {