fix(desktop): verify privileged ipc senders (#2360)

This commit is contained in:
Hampus
2026-09-02 17:26:45 +02:00
committed by GitHub
parent de2ea99928
commit a53f5d1289
6 changed files with 56 additions and 9 deletions
+5 -1
View File
@@ -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',
+3 -1
View File
@@ -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);
}
}
+1 -1
View File
@@ -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;