mirror of
https://github.com/fluxerapp/fluxer.git
synced 2026-09-02 21:04:06 +03:00
fix(desktop): verify privileged ipc senders (#2360)
This commit is contained in:
@@ -35,6 +35,7 @@ import {openExternalDeduped} from '@electron/main/OpenExternal';
|
|||||||
import {getStatus as getOpenH264Status, setEnabled as setOpenH264Enabled} from '@electron/main/OpenH264Manager';
|
import {getStatus as getOpenH264Status, setEnabled as setOpenH264Enabled} from '@electron/main/OpenH264Manager';
|
||||||
import {registerPasskeyHandlers} from '@electron/main/Passkeys';
|
import {registerPasskeyHandlers} from '@electron/main/Passkeys';
|
||||||
import {getAppMetricsSnapshot, getDesktopInfo, getGpuInfo} from '@electron/main/PlatformInfo';
|
import {getAppMetricsSnapshot, getDesktopInfo, getGpuInfo} from '@electron/main/PlatformInfo';
|
||||||
|
import {requirePrivilegedRendererDocumentSender} from '@electron/main/PrivilegedRendererDocuments';
|
||||||
import {getStreamerModeCaptureAppStatus} from '@electron/main/StreamerModeProcessDetection';
|
import {getStreamerModeCaptureAppStatus} from '@electron/main/StreamerModeProcessDetection';
|
||||||
import {
|
import {
|
||||||
acquireStreamingPriority,
|
acquireStreamingPriority,
|
||||||
@@ -347,7 +348,8 @@ export function registerIpcHandlers(): void {
|
|||||||
ipcMain.handle('clipboard-read-text', (): string => {
|
ipcMain.handle('clipboard-read-text', (): string => {
|
||||||
return clipboard.readText();
|
return clipboard.readText();
|
||||||
});
|
});
|
||||||
ipcMain.handle('clipboard-write-file', async (_event, rawOptions: unknown): Promise<ClipboardWriteFileResult> => {
|
ipcMain.handle('clipboard-write-file', async (event, rawOptions: unknown): Promise<ClipboardWriteFileResult> => {
|
||||||
|
requirePrivilegedRendererDocumentSender(event, 'clipboard-write-file');
|
||||||
try {
|
try {
|
||||||
return await copyRemoteFileToClipboard(parseClipboardWriteFileOptions(rawOptions));
|
return await copyRemoteFileToClipboard(parseClipboardWriteFileOptions(rawOptions));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -355,6 +357,7 @@ export function registerIpcHandlers(): void {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
ipcMain.handle('clipboard-paste', (event): void => {
|
ipcMain.handle('clipboard-paste', (event): void => {
|
||||||
|
requirePrivilegedRendererDocumentSender(event, 'clipboard-paste');
|
||||||
event.sender.paste();
|
event.sender.paste();
|
||||||
});
|
});
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
@@ -386,6 +389,7 @@ export function registerIpcHandlers(): void {
|
|||||||
defaultPath: string;
|
defaultPath: string;
|
||||||
},
|
},
|
||||||
): Promise<DownloadFileResult> => {
|
): Promise<DownloadFileResult> => {
|
||||||
|
requirePrivilegedRendererDocumentSender(event, 'download-file');
|
||||||
const win = BrowserWindow.fromWebContents(event.sender);
|
const win = BrowserWindow.fromWebContents(event.sender);
|
||||||
if (!win) {
|
if (!win) {
|
||||||
return {success: false, error: 'No window found'};
|
return {success: false, error: 'No window found'};
|
||||||
|
|||||||
@@ -150,6 +150,9 @@ function loadNativeScreenCapture({platform = 'linux', addon, tccStatus = 'not-de
|
|||||||
normalizeScreenCaptureDimension: (value) => value,
|
normalizeScreenCaptureDimension: (value) => value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (specifier === './PrivilegedRendererDocuments') {
|
||||||
|
return {requirePrivilegedRendererDocumentSender: () => {}};
|
||||||
|
}
|
||||||
throw new Error(`Unexpected import: ${specifier}`);
|
throw new Error(`Unexpected import: ${specifier}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import type {
|
|||||||
import {ipcMain} from 'electron';
|
import {ipcMain} from 'electron';
|
||||||
import {getTccStatus} from './MacTcc';
|
import {getTccStatus} from './MacTcc';
|
||||||
import {isValidStartOptions, normalizeScreenCaptureDimension} from './NativeScreenCaptureValidation';
|
import {isValidStartOptions, normalizeScreenCaptureDimension} from './NativeScreenCaptureValidation';
|
||||||
|
import {requirePrivilegedRendererDocumentSender} from './PrivilegedRendererDocuments';
|
||||||
|
|
||||||
const logger = createChildLogger('NativeScreenCapture');
|
const logger = createChildLogger('NativeScreenCapture');
|
||||||
const requireModule = createRequire(import.meta.url);
|
const requireModule = createRequire(import.meta.url);
|
||||||
@@ -944,14 +945,16 @@ export function registerNativeScreenCaptureHandlers(): void {
|
|||||||
'native-screen-capture:get-availability',
|
'native-screen-capture:get-availability',
|
||||||
(): Promise<NativeScreenCaptureAvailability> => getNativeScreenCaptureAvailability(),
|
(): Promise<NativeScreenCaptureAvailability> => getNativeScreenCaptureAvailability(),
|
||||||
);
|
);
|
||||||
ipcMain.handle(
|
ipcMain.handle('native-screen-capture:list-sources', (event): Promise<Array<NativeScreenCaptureSource>> => {
|
||||||
'native-screen-capture:list-sources',
|
requirePrivilegedRendererDocumentSender(event, 'native-screen-capture:list-sources');
|
||||||
(): Promise<Array<NativeScreenCaptureSource>> => listNativeScreenCaptureSources(),
|
return listNativeScreenCaptureSources();
|
||||||
);
|
});
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
'native-screen-capture:start',
|
'native-screen-capture:start',
|
||||||
(event, options: NativeScreenCaptureStartOptions): Promise<NativeScreenCaptureStartResult> =>
|
(event, options: NativeScreenCaptureStartOptions): Promise<NativeScreenCaptureStartResult> => {
|
||||||
startNativeScreenCapture(event.sender, options),
|
requirePrivilegedRendererDocumentSender(event, 'native-screen-capture:start');
|
||||||
|
return startNativeScreenCapture(event.sender, options);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
'native-screen-capture:get-diagnostics',
|
'native-screen-capture:get-diagnostics',
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type {NotificationOptions} from '@electron/common/Types';
|
|||||||
import {getNativeNotificationsMode} from '@electron/main/LaunchOptions';
|
import {getNativeNotificationsMode} from '@electron/main/LaunchOptions';
|
||||||
import {resolveNotificationIcon} from '@electron/main/NotificationIcon';
|
import {resolveNotificationIcon} from '@electron/main/NotificationIcon';
|
||||||
import {shouldPlayNotificationSound} from '@electron/main/NotificationState';
|
import {shouldPlayNotificationSound} from '@electron/main/NotificationState';
|
||||||
|
import {requirePrivilegedRendererDocumentSender} from '@electron/main/PrivilegedRendererDocuments';
|
||||||
import {type BrowserWindow, ipcMain, Notification, nativeImage} from 'electron';
|
import {type BrowserWindow, ipcMain, Notification, nativeImage} from 'electron';
|
||||||
|
|
||||||
const logger = createChildLogger('Notifications');
|
const logger = createChildLogger('Notifications');
|
||||||
@@ -319,11 +320,12 @@ export function registerNotificationIpcHandlers(getMainWindow: () => BrowserWind
|
|||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
'show-notification',
|
'show-notification',
|
||||||
async (
|
async (
|
||||||
_event,
|
event,
|
||||||
options: NotificationOptions,
|
options: NotificationOptions,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
id: string;
|
id: string;
|
||||||
}> => {
|
}> => {
|
||||||
|
requirePrivilegedRendererDocumentSender(event, 'show-notification');
|
||||||
const id = getNotificationId(options);
|
const id = getNotificationId(options);
|
||||||
if (process.platform === 'linux') {
|
if (process.platform === 'linux') {
|
||||||
await showLinuxNativeNotification(id, options, getMainWindow);
|
await showLinuxNativeNotification(id, options, getMainWindow);
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
import {isTrustedOrigin} from '@electron/main/Window';
|
||||||
|
import type {IpcMainInvokeEvent} from 'electron';
|
||||||
|
|
||||||
|
export class UntrustedRendererDocumentSenderError extends Error {
|
||||||
|
public constructor(channel: string) {
|
||||||
|
super(`${channel} is only reachable from a trusted top-level renderer document`);
|
||||||
|
this.name = 'UntrustedRendererDocumentSenderError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrivilegedRendererDocumentSender(event: IpcMainInvokeEvent): boolean {
|
||||||
|
const frame = event.senderFrame;
|
||||||
|
if (frame == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (frame.detached) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (frame.parent != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isTrustedOrigin(frame.url);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requirePrivilegedRendererDocumentSender(event: IpcMainInvokeEvent, channel: string): void {
|
||||||
|
if (!isPrivilegedRendererDocumentSender(event)) {
|
||||||
|
throw new UntrustedRendererDocumentSenderError(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -106,7 +106,7 @@ function getOrigin(url?: string): string | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTrustedOrigin(url?: string): boolean {
|
export function isTrustedOrigin(url?: string): boolean {
|
||||||
const origin = getOrigin(url);
|
const origin = getOrigin(url);
|
||||||
if (!origin) return false;
|
if (!origin) return false;
|
||||||
if (trustedWebOrigins.has(origin)) return true;
|
if (trustedWebOrigins.has(origin)) return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user