From 421c93c15e4e8ea618e87c2536c5fdb8991254f8 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Wed, 25 Oct 2023 11:23:40 -0400 Subject: [PATCH 1/3] refactor: ensure IpcRenderer is not bridgable --- lib/renderer/api/ipc-renderer.ts | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/renderer/api/ipc-renderer.ts b/lib/renderer/api/ipc-renderer.ts index b637f19271953..c82f8621237a7 100644 --- a/lib/renderer/api/ipc-renderer.ts +++ b/lib/renderer/api/ipc-renderer.ts @@ -3,30 +3,30 @@ import { EventEmitter } from 'events'; const { ipc } = process._linkedBinding('electron_renderer_ipc'); const internal = false; +class IpcRenderer extends EventEmitter implements Electron.IpcRenderer { + send (channel: string, ...args: any[]) { + return ipc.send(internal, channel, args); + } -const ipcRenderer = new EventEmitter() as Electron.IpcRenderer; -ipcRenderer.send = function (channel, ...args) { - return ipc.send(internal, channel, args); -}; - -ipcRenderer.sendSync = function (channel, ...args) { - return ipc.sendSync(internal, channel, args); -}; + sendSync (channel: string, ...args: any[]) { + return ipc.sendSync(internal, channel, args); + } -ipcRenderer.sendToHost = function (channel, ...args) { - return ipc.sendToHost(channel, args); -}; + sendToHost (channel: string, ...args: any[]) { + return ipc.sendToHost(channel, args); + } -ipcRenderer.invoke = async function (channel, ...args) { - const { error, result } = await ipc.invoke(internal, channel, args); - if (error) { - throw new Error(`Error invoking remote method '${channel}': ${error}`); + async invoke (channel: string, ...args: any[]) { + const { error, result } = await ipc.invoke(internal, channel, args); + if (error) { + throw new Error(`Error invoking remote method '${channel}': ${error}`); + } + return result; } - return result; -}; -ipcRenderer.postMessage = function (channel: string, message: any, transferables: any) { - return ipc.postMessage(channel, message, transferables); -}; + postMessage (channel: string, message: any, transferables: any) { + return ipc.postMessage(channel, message, transferables); + } +} -export default ipcRenderer; +export default new IpcRenderer(); From d48bd5a45f611993f2c361e8e1509d4ff4746059 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 30 Oct 2023 22:48:01 -0700 Subject: [PATCH 2/3] chore: add notes to breaking-changes --- docs/breaking-changes.md | 13 ++++++++++++ lib/renderer/ipc-renderer-internal.ts | 30 ++++++++++++++------------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/docs/breaking-changes.md b/docs/breaking-changes.md index ddffa5f081368..682f1bb55461f 100644 --- a/docs/breaking-changes.md +++ b/docs/breaking-changes.md @@ -14,6 +14,19 @@ This document uses the following convention to categorize breaking changes: ## Planned Breaking API Changes (29.0) +### Behavior Changed: `ipcRenderer` can no longer be sent over the `contextBridge` + +Attempting to send `ipcRenderer` as an object over the `contextBridge` will now result in +an empty object on the receiving side of the bridge. This change was made to remove / mitigate +a security footgun, you should not directly expose ipcRenderer or it's methods over the bridge. +Instead provide a safe wrapper like below: + +```js +contextBridge.exposeInMainWorld('app', { + onEvent: (cb) => ipcRenderer.on('foo', (e, ...args) => cb(args)) +}) +``` + ### Removed: `renderer-process-crashed` event on `app` The `renderer-process-crashed` event on `app` has been removed. diff --git a/lib/renderer/ipc-renderer-internal.ts b/lib/renderer/ipc-renderer-internal.ts index f37ea0d724583..da832d2e47bdf 100644 --- a/lib/renderer/ipc-renderer-internal.ts +++ b/lib/renderer/ipc-renderer-internal.ts @@ -4,20 +4,22 @@ const { ipc } = process._linkedBinding('electron_renderer_ipc'); const internal = true; -export const ipcRendererInternal = new EventEmitter() as any as ElectronInternal.IpcRendererInternal; +class IpcRendererInternal extends EventEmitter implements ElectronInternal.IpcRendererInternal { + send (channel: string, ...args: any[]) { + return ipc.send(internal, channel, args); + } -ipcRendererInternal.send = function (channel, ...args) { - return ipc.send(internal, channel, args); -}; + sendSync (channel: string, ...args: any[]) { + return ipc.sendSync(internal, channel, args); + } -ipcRendererInternal.sendSync = function (channel, ...args) { - return ipc.sendSync(internal, channel, args); -}; + async invoke (channel: string, ...args: any[]) { + const { error, result } = await ipc.invoke(internal, channel, args); + if (error) { + throw new Error(`Error invoking remote method '${channel}': ${error}`); + } + return result; + }; +} -ipcRendererInternal.invoke = async function (channel: string, ...args: any[]) { - const { error, result } = await ipc.invoke(internal, channel, args); - if (error) { - throw new Error(`Error invoking remote method '${channel}': ${error}`); - } - return result; -}; +export const ipcRendererInternal = new IpcRendererInternal(); From 8a71f9cf57b7412304ccd37fd1638d3a87d7fed8 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 31 Oct 2023 10:28:41 -0700 Subject: [PATCH 3/3] spec: fix test that bridged ipcrenderer --- spec/fixtures/apps/libuv-hang/preload.js | 3 ++- spec/fixtures/apps/libuv-hang/renderer.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/fixtures/apps/libuv-hang/preload.js b/spec/fixtures/apps/libuv-hang/preload.js index 2629549fbf61b..d788aec42919f 100644 --- a/spec/fixtures/apps/libuv-hang/preload.js +++ b/spec/fixtures/apps/libuv-hang/preload.js @@ -1,7 +1,8 @@ const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('api', { - ipcRenderer, + // This is not safe, do not copy this code into your app + invoke: (...args) => ipcRenderer.invoke(...args), run: async () => { const { promises: fs } = require('node:fs'); for (let i = 0; i < 10; i++) { diff --git a/spec/fixtures/apps/libuv-hang/renderer.js b/spec/fixtures/apps/libuv-hang/renderer.js index cdbc4aab4d07e..eb822ca5191fa 100644 --- a/spec/fixtures/apps/libuv-hang/renderer.js +++ b/spec/fixtures/apps/libuv-hang/renderer.js @@ -1,6 +1,6 @@ -const { run, ipcRenderer } = window.api; +const { run, invoke } = window.api; run().then(async () => { - const count = await ipcRenderer.invoke('reload-successful'); + const count = await invoke('reload-successful'); if (count < 3) location.reload(); }).catch(console.log);