fix potential sandbox escape via custom vencordDir
This commit is contained in:
parent
ec3d83f7ca
commit
1f12d270ec
9 changed files with 84 additions and 20 deletions
|
@ -37,7 +37,8 @@ if (existsSync(LEGACY_DATA_DIR)) {
|
||||||
console.error("Migration failed", e);
|
console.error("Migration failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.setPath("sessionData", join(DATA_DIR, "sessionData"));
|
const SESSION_DATA_DIR = join(DATA_DIR, "sessionData");
|
||||||
|
app.setPath("sessionData", SESSION_DATA_DIR);
|
||||||
|
|
||||||
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
|
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
|
||||||
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
|
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
|
||||||
|
@ -47,7 +48,8 @@ export const VENCORD_THEMES_DIR = join(DATA_DIR, "themes");
|
||||||
// needs to be inline require because of circular dependency
|
// needs to be inline require because of circular dependency
|
||||||
// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
|
// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
|
||||||
export const VENCORD_FILES_DIR =
|
export const VENCORD_FILES_DIR =
|
||||||
(require("./settings") as typeof import("./settings")).Settings.store.vencordDir || join(DATA_DIR, "vencordDist");
|
(require("./settings") as typeof import("./settings")).State.store.vencordDir ||
|
||||||
|
join(SESSION_DATA_DIR, "vencordFiles");
|
||||||
|
|
||||||
export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`;
|
export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`;
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { setBadgeCount } from "./appBadge";
|
||||||
import { autoStart } from "./autoStart";
|
import { autoStart } from "./autoStart";
|
||||||
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants";
|
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants";
|
||||||
import { mainWin } from "./mainWindow";
|
import { mainWin } from "./mainWindow";
|
||||||
import { Settings } from "./settings";
|
import { Settings, State } from "./settings";
|
||||||
import { handle, handleSync } from "./utils/ipcWrappers";
|
import { handle, handleSync } from "./utils/ipcWrappers";
|
||||||
import { PopoutWindows } from "./utils/popout";
|
import { PopoutWindows } from "./utils/popout";
|
||||||
import { isDeckGameMode, showGamePage } from "./utils/steamOS";
|
import { isDeckGameMode, showGamePage } from "./utils/steamOS";
|
||||||
|
@ -105,7 +105,15 @@ handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => {
|
||||||
e.sender.session.addWordToSpellCheckerDictionary(word);
|
e.sender.session.addWordToSpellCheckerDictionary(word);
|
||||||
});
|
});
|
||||||
|
|
||||||
handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
|
handleSync(IpcEvents.GET_VENCORD_DIR, e => (e.returnValue = State.store.vencordDir));
|
||||||
|
|
||||||
|
handle(IpcEvents.SELECT_VENCORD_DIR, async (_e, value?: null) => {
|
||||||
|
console.log(value);
|
||||||
|
if (value === null) {
|
||||||
|
delete State.store.vencordDir;
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
const res = await dialog.showOpenDialog(mainWin!, {
|
const res = await dialog.showOpenDialog(mainWin!, {
|
||||||
properties: ["openDirectory"]
|
properties: ["openDirectory"]
|
||||||
});
|
});
|
||||||
|
@ -114,7 +122,9 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
|
||||||
const dir = res.filePaths[0];
|
const dir = res.filePaths[0];
|
||||||
if (!isValidVencordInstall(dir)) return "invalid";
|
if (!isValidVencordInstall(dir)) return "invalid";
|
||||||
|
|
||||||
return dir;
|
State.store.vencordDir = dir;
|
||||||
|
|
||||||
|
return "ok";
|
||||||
});
|
});
|
||||||
|
|
||||||
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
|
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { existsSync, mkdirSync } from "fs";
|
import { mkdirSync } from "fs";
|
||||||
|
import { access, constants as FsConstants } from "fs/promises";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
|
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
|
||||||
|
@ -56,12 +57,18 @@ export async function downloadVencordFiles() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidVencordInstall(dir: string) {
|
const existsAsync = (path: string) =>
|
||||||
return FILES_TO_DOWNLOAD.every(f => existsSync(join(dir, f)));
|
access(path, FsConstants.F_OK)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
|
||||||
|
export async function isValidVencordInstall(dir: string) {
|
||||||
|
return Promise.all(FILES_TO_DOWNLOAD.map(f => existsAsync(join(dir, f)))).then(arr => !arr.includes(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ensureVencordFiles() {
|
export async function ensureVencordFiles() {
|
||||||
if (isValidVencordInstall(VENCORD_FILES_DIR)) return;
|
if (await isValidVencordInstall(VENCORD_FILES_DIR)) return;
|
||||||
|
|
||||||
mkdirSync(VENCORD_FILES_DIR, { recursive: true });
|
mkdirSync(VENCORD_FILES_DIR, { recursive: true });
|
||||||
|
|
||||||
await downloadVencordFiles();
|
await downloadVencordFiles();
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
import { Node } from "@vencord/venmic";
|
import { Node } from "@vencord/venmic";
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import type { Settings } from "shared/settings";
|
import type { Settings } from "shared/settings";
|
||||||
import type { LiteralUnion } from "type-fest";
|
|
||||||
|
|
||||||
import { IpcEvents } from "../shared/IpcEvents";
|
import { IpcEvents } from "../shared/IpcEvents";
|
||||||
import { invoke, sendSync } from "./typedIpc";
|
import { invoke, sendSync } from "./typedIpc";
|
||||||
|
@ -34,7 +33,8 @@ export const VesktopNative = {
|
||||||
},
|
},
|
||||||
fileManager: {
|
fileManager: {
|
||||||
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
|
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
|
||||||
selectVencordDir: () => invoke<LiteralUnion<"cancelled" | "invalid", string>>(IpcEvents.SELECT_VENCORD_DIR)
|
getVencordDir: () => sendSync<string | undefined>(IpcEvents.GET_VENCORD_DIR),
|
||||||
|
selectVencordDir: (value?: null) => invoke<"cancelled" | "invalid" | "ok">(IpcEvents.SELECT_VENCORD_DIR, value)
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
||||||
|
|
|
@ -4,24 +4,28 @@
|
||||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useForceUpdater } from "@vencord/types/utils";
|
||||||
import { Button, Forms, Toasts } from "@vencord/types/webpack/common";
|
import { Button, Forms, Toasts } from "@vencord/types/webpack/common";
|
||||||
|
|
||||||
import { SettingsComponent } from "./Settings";
|
import { SettingsComponent } from "./Settings";
|
||||||
|
|
||||||
export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
|
export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
|
||||||
|
const forceUpdate = useForceUpdater();
|
||||||
|
const vencordDir = VesktopNative.fileManager.getVencordDir();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
Vencord files are loaded from{" "}
|
Vencord files are loaded from{" "}
|
||||||
{settings.vencordDir ? (
|
{vencordDir ? (
|
||||||
<a
|
<a
|
||||||
href="about:blank"
|
href="about:blank"
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
VesktopNative.fileManager.showItemInFolder(settings.vencordDir!);
|
VesktopNative.fileManager.showItemInFolder(vencordDir!);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{settings.vencordDir}
|
{vencordDir}
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
"the default location"
|
"the default location"
|
||||||
|
@ -34,7 +38,14 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
|
||||||
const choice = await VesktopNative.fileManager.selectVencordDir();
|
const choice = await VesktopNative.fileManager.selectVencordDir();
|
||||||
switch (choice) {
|
switch (choice) {
|
||||||
case "cancelled":
|
case "cancelled":
|
||||||
return;
|
break;
|
||||||
|
case "ok":
|
||||||
|
Toasts.show({
|
||||||
|
message: "Vencord install changed. Fully restart Vesktop to apply.",
|
||||||
|
id: Toasts.genId(),
|
||||||
|
type: Toasts.Type.SUCCESS
|
||||||
|
});
|
||||||
|
break;
|
||||||
case "invalid":
|
case "invalid":
|
||||||
Toasts.show({
|
Toasts.show({
|
||||||
message:
|
message:
|
||||||
|
@ -42,9 +53,9 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
|
||||||
id: Toasts.genId(),
|
id: Toasts.genId(),
|
||||||
type: Toasts.Type.FAILURE
|
type: Toasts.Type.FAILURE
|
||||||
});
|
});
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
settings.vencordDir = choice;
|
forceUpdate();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Change
|
Change
|
||||||
|
@ -52,7 +63,10 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
|
||||||
<Button
|
<Button
|
||||||
size={Button.Sizes.SMALL}
|
size={Button.Sizes.SMALL}
|
||||||
color={Button.Colors.RED}
|
color={Button.Colors.RED}
|
||||||
onClick={() => (settings.vencordDir = void 0)}
|
onClick={async () => {
|
||||||
|
await VesktopNative.fileManager.selectVencordDir(null);
|
||||||
|
forceUpdate();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Reset
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -13,7 +13,7 @@ console.log("read if cute :3");
|
||||||
|
|
||||||
export * as Components from "./components";
|
export * as Components from "./components";
|
||||||
import { findByPropsLazy, onceReady } from "@vencord/types/webpack";
|
import { findByPropsLazy, onceReady } from "@vencord/types/webpack";
|
||||||
import { FluxDispatcher } from "@vencord/types/webpack/common";
|
import { Alerts, FluxDispatcher } from "@vencord/types/webpack/common";
|
||||||
|
|
||||||
import SettingsUi from "./components/settings/Settings";
|
import SettingsUi from "./components/settings/Settings";
|
||||||
import { Settings } from "./settings";
|
import { Settings } from "./settings";
|
||||||
|
@ -59,3 +59,19 @@ VesktopNative.arrpc.onActivity(async data => {
|
||||||
|
|
||||||
arRPC.handleEvent(new MessageEvent("message", { data }));
|
arRPC.handleEvent(new MessageEvent("message", { data }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: remove soon
|
||||||
|
const vencordDir = "vencordDir" as keyof typeof Settings.store;
|
||||||
|
if (Settings.store[vencordDir]) {
|
||||||
|
onceReady.then(() =>
|
||||||
|
setTimeout(
|
||||||
|
() =>
|
||||||
|
Alerts.show({
|
||||||
|
title: "Custom Vencord Location",
|
||||||
|
body: "Due to changes in Vesktop, your custom Vencord location had to bee reset. Please set it again in the settings.",
|
||||||
|
onConfirm: () => delete Settings.store[vencordDir]
|
||||||
|
}),
|
||||||
|
5000
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ export const enum IpcEvents {
|
||||||
GET_SETTINGS = "VCD_GET_SETTINGS",
|
GET_SETTINGS = "VCD_GET_SETTINGS",
|
||||||
SET_SETTINGS = "VCD_SET_SETTINGS",
|
SET_SETTINGS = "VCD_SET_SETTINGS",
|
||||||
|
|
||||||
|
GET_VENCORD_DIR = "VCD_GET_VENCORD_DIR",
|
||||||
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",
|
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",
|
||||||
|
|
||||||
UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA",
|
UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA",
|
||||||
|
|
3
src/shared/settings.d.ts
vendored
3
src/shared/settings.d.ts
vendored
|
@ -8,7 +8,6 @@ import type { Rectangle } from "electron";
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
discordBranch?: "stable" | "canary" | "ptb";
|
discordBranch?: "stable" | "canary" | "ptb";
|
||||||
vencordDir?: string;
|
|
||||||
transparencyOption?: "none" | "mica" | "tabbed" | "acrylic";
|
transparencyOption?: "none" | "mica" | "tabbed" | "acrylic";
|
||||||
tray?: boolean;
|
tray?: boolean;
|
||||||
minimizeToTray?: boolean;
|
minimizeToTray?: boolean;
|
||||||
|
@ -54,4 +53,6 @@ export interface State {
|
||||||
firstLaunch?: boolean;
|
firstLaunch?: boolean;
|
||||||
|
|
||||||
steamOSLayoutVersion?: number;
|
steamOSLayoutVersion?: number;
|
||||||
|
|
||||||
|
vencordDir?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,19 @@ export class SettingsStore<T extends object> {
|
||||||
self.pathListeners.get(setPath)?.forEach(cb => cb(value));
|
self.pathListeners.get(setPath)?.forEach(cb => cb(value));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
},
|
||||||
|
deleteProperty(target, key: string) {
|
||||||
|
if (!(key in target)) return true;
|
||||||
|
|
||||||
|
const res = Reflect.deleteProperty(target, key);
|
||||||
|
if (!res) return false;
|
||||||
|
|
||||||
|
const setPath = `${path}${path && "."}${key}`;
|
||||||
|
|
||||||
|
self.globalListeners.forEach(cb => cb(root, setPath));
|
||||||
|
self.pathListeners.get(setPath)?.forEach(cb => cb(undefined));
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue