mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-20 11:57:02 -04:00
Allow users to manually whitelist Domains for use in themes (#3476)
This commit is contained in:
parent
7f2c4a3566
commit
ed5ed4b80a
12 changed files with 313 additions and 46 deletions
125
src/main/csp/manager.ts
Normal file
125
src/main/csp/manager.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { NativeSettings } from "@main/settings";
|
||||
import { IpcEvents } from "@shared/IpcEvents";
|
||||
import { dialog, ipcMain, IpcMainInvokeEvent } from "electron";
|
||||
|
||||
import { CspPolicies, ImageAndCssSrc } from ".";
|
||||
|
||||
export type CspRequestResult = "invalid" | "cancelled" | "unchecked" | "ok" | "conflict";
|
||||
|
||||
export function registerCspIpcHandlers() {
|
||||
ipcMain.handle(IpcEvents.CSP_REMOVE_OVERRIDE, removeCspRule);
|
||||
ipcMain.handle(IpcEvents.CSP_REQUEST_ADD_OVERRIDE, addCspRule);
|
||||
ipcMain.handle(IpcEvents.CSP_IS_DOMAIN_ALLOWED, isDomainAllowed);
|
||||
}
|
||||
|
||||
function validate(url: string, directives: string[]) {
|
||||
try {
|
||||
const { hostname } = new URL(url);
|
||||
|
||||
if (/[;'"\\]/.test(hostname)) return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (directives.length === 0) return false;
|
||||
if (directives.some(d => !ImageAndCssSrc.includes(d))) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getMessage(url: string, directives: string[], callerName: string) {
|
||||
const domain = new URL(url).hostname;
|
||||
|
||||
const message = `${callerName} wants to allow connections to ${domain}`;
|
||||
|
||||
let detail =
|
||||
`Unless you recognise and fully trust ${domain}, you should cancel this request!\n\n` +
|
||||
`You will have to fully close and restart ${IS_DISCORD_DESKTOP ? "Discord" : "Vesktop"} for the changes to take effect.`;
|
||||
|
||||
if (directives.length === 1 && directives[0] === "connect-src") {
|
||||
return { message, detail };
|
||||
}
|
||||
|
||||
const contentTypes = directives
|
||||
.filter(type => type !== "connect-src")
|
||||
.map(type => {
|
||||
switch (type) {
|
||||
case "img-src":
|
||||
return "Images";
|
||||
case "style-src":
|
||||
return "CSS & Themes";
|
||||
case "font-src":
|
||||
return "Fonts";
|
||||
default:
|
||||
throw new Error(`Illegal CSP directive: ${type}`);
|
||||
}
|
||||
})
|
||||
.sort()
|
||||
.join(", ");
|
||||
|
||||
detail = `The following types of content will be allowed to load from ${domain}:\n${contentTypes}\n\n${detail}`;
|
||||
|
||||
return { message, detail };
|
||||
}
|
||||
|
||||
async function addCspRule(_: IpcMainInvokeEvent, url: string, directives: string[], callerName: string): Promise<CspRequestResult> {
|
||||
if (!validate(url, directives)) {
|
||||
return "invalid";
|
||||
}
|
||||
|
||||
const domain = new URL(url).hostname;
|
||||
|
||||
if (domain in NativeSettings.store.customCspRules) {
|
||||
return "conflict";
|
||||
}
|
||||
|
||||
const { checkboxChecked, response } = await dialog.showMessageBox({
|
||||
...getMessage(url, directives, callerName),
|
||||
type: callerName ? "info" : "warning",
|
||||
title: "Vencord Host Permissions",
|
||||
buttons: ["Cancel", "Allow"],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
checkboxLabel: `I fully trust ${domain} and understand the risks of allowing connections to it.`,
|
||||
checkboxChecked: false,
|
||||
});
|
||||
|
||||
if (response !== 1) {
|
||||
return "cancelled";
|
||||
}
|
||||
|
||||
if (!checkboxChecked) {
|
||||
return "unchecked";
|
||||
}
|
||||
|
||||
NativeSettings.store.customCspRules[domain] = directives;
|
||||
return "ok";
|
||||
}
|
||||
|
||||
function removeCspRule(_: IpcMainInvokeEvent, domain: string) {
|
||||
if (domain in NativeSettings.store.customCspRules) {
|
||||
delete NativeSettings.store.customCspRules[domain];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isDomainAllowed(_: IpcMainInvokeEvent, url: string, directives: string[]) {
|
||||
try {
|
||||
const domain = new URL(url).hostname;
|
||||
|
||||
const ruleForDomain = CspPolicies[domain] ?? NativeSettings.store.customCspRules[domain];
|
||||
if (!ruleForDomain) return false;
|
||||
|
||||
return directives.every(d => ruleForDomain.includes(d));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue