|
1 | 1 | import { contextBridge, ipcRenderer } from "electron" |
2 | 2 |
|
3 | 3 | // --------- Expose some API to the Renderer process --------- |
| 4 | +// Whitelist of allowed IPC channels for security |
| 5 | +const ALLOWED_INVOKE_CHANNELS = [ |
| 6 | + "fla:open-file", |
| 7 | + "fla:get-recent-logs", |
| 8 | + "fla:clear-recent-logs", |
| 9 | + "missions:get-save-mission-file-path", |
| 10 | + "app:get-node-env", |
| 11 | + "app:get-version", |
| 12 | + "app:is-mac", |
| 13 | + "settings:fetch-settings", |
| 14 | + "settings:save-settings", |
| 15 | + "app:open-webcam-window", |
| 16 | + "app:close-webcam-window", |
| 17 | + "app:open-about-window", |
| 18 | + "app:close-about-window", |
| 19 | + "app:open-link-stats-window", |
| 20 | + "app:close-link-stats-window", |
| 21 | + "app:update-link-stats", |
| 22 | +] |
| 23 | + |
| 24 | +const ALLOWED_SEND_CHANNELS = [ |
| 25 | + "window:close", |
| 26 | + "window:minimise", |
| 27 | + "window:maximise", |
| 28 | + "window:reload", |
| 29 | + "window:force-reload", |
| 30 | + "window:toggle-developer-tools", |
| 31 | + "window:actual-size", |
| 32 | + "window:toggle-fullscreen", |
| 33 | + "window:zoom-in", |
| 34 | + "window:zoom-out", |
| 35 | + "window:open-file-in-explorer", |
| 36 | +] |
| 37 | + |
| 38 | +const ALLOWED_ON_CHANNELS = [ |
| 39 | + "main-process-message", |
| 40 | + "app:webcam-closed", |
| 41 | + "app:send-link-stats", |
| 42 | + "fla:log-parse-progress", |
| 43 | +] |
| 44 | + |
4 | 45 | contextBridge.exposeInMainWorld("ipcRenderer", { |
5 | | - ...withPrototype(ipcRenderer), |
6 | | - loadFile: (data) => ipcRenderer.invoke("fla:open-file", data), |
7 | | - getRecentLogs: () => ipcRenderer.invoke("fla:get-recent-logs"), |
8 | | - clearRecentLogs: () => ipcRenderer.invoke("fla:clear-recent-logs"), |
9 | | - getSaveMissionFilePath: (options) => |
10 | | - ipcRenderer.invoke("missions:get-save-mission-file-path", options), |
11 | | - getNodeEnv: () => ipcRenderer.invoke("app:get-node-env"), |
12 | | - getVersion: () => ipcRenderer.invoke("app:get-version"), |
13 | | - getSettings: () => ipcRenderer.invoke("getSettings"), |
14 | | - saveSettings: (settings) => ipcRenderer.invoke("setSettings", settings), |
15 | | - openWebcamWindow: (id, name, aspect) => |
16 | | - ipcRenderer.invoke("openWebcamWindow", id, name, aspect), |
17 | | - closeWebcamWindow: () => ipcRenderer.invoke("closeWebcamWindow"), |
18 | | - onCameraWindowClose: (callback) => |
19 | | - ipcRenderer.on("webcam-closed", () => callback()), |
20 | | - openAboutWindow: () => ipcRenderer.invoke("openAboutWindow"), |
21 | | - closeAboutWindow: () => ipcRenderer.invoke("closeAboutWindow"), |
22 | | - openLinkStatsWindow: () => ipcRenderer.invoke("openLinkStatsWindow"), |
23 | | - closeLinkStatsWindow: () => ipcRenderer.invoke("closeLinkStatsWindow"), |
24 | | - updateLinkStats: (linkStats) => |
25 | | - ipcRenderer.invoke("update-link-stats", linkStats), |
26 | | - onGetLinkStats: (callback) => |
27 | | - ipcRenderer.on("send-link-stats", (_, stats) => callback(stats)), |
28 | | -}) |
| 46 | + // Secure invoke method - only allows whitelisted channels |
| 47 | + invoke: (channel, ...args) => { |
| 48 | + if (ALLOWED_INVOKE_CHANNELS.includes(channel)) { |
| 49 | + return ipcRenderer.invoke(channel, ...args) |
| 50 | + } |
| 51 | + throw new Error(`IPC invoke channel '${channel}' is not allowed`) |
| 52 | + }, |
29 | 53 |
|
30 | | -// `exposeInMainWorld` can't detect attributes and methods of `prototype`, manually patching it. |
31 | | -function withPrototype(obj) { |
32 | | - const protos = Object.getPrototypeOf(obj) |
| 54 | + // Secure send method - only allows whitelisted channels |
| 55 | + send: (channel, ...args) => { |
| 56 | + if (ALLOWED_SEND_CHANNELS.includes(channel)) { |
| 57 | + return ipcRenderer.send(channel, ...args) |
| 58 | + } |
| 59 | + throw new Error(`IPC send channel '${channel}' is not allowed`) |
| 60 | + }, |
33 | 61 |
|
34 | | - for (const [key, value] of Object.entries(protos)) { |
35 | | - if (Object.prototype.hasOwnProperty.call(obj, key)) continue |
| 62 | + // Secure on method - only allows whitelisted channels |
| 63 | + on: (channel, callback) => { |
| 64 | + if (ALLOWED_ON_CHANNELS.includes(channel)) { |
| 65 | + return ipcRenderer.on(channel, callback) |
| 66 | + } |
| 67 | + throw new Error(`IPC on channel '${channel}' is not allowed`) |
| 68 | + }, |
36 | 69 |
|
37 | | - if (typeof value === "function") { |
38 | | - // Some native APIs, like `NodeJS.EventEmitter['on']`, don't work in the Renderer process. Wrapping them into a function. |
39 | | - obj[key] = function (...args) { |
40 | | - return value.call(obj, ...args) |
41 | | - } |
42 | | - } else { |
43 | | - obj[key] = value |
| 70 | + // Secure removeAllListeners - only for whitelisted channels |
| 71 | + removeAllListeners: (channel) => { |
| 72 | + if (ALLOWED_ON_CHANNELS.includes(channel)) { |
| 73 | + return ipcRenderer.removeAllListeners(channel) |
44 | 74 | } |
45 | | - } |
46 | | - return obj |
47 | | -} |
| 75 | + throw new Error( |
| 76 | + `IPC removeAllListeners channel '${channel}' is not allowed`, |
| 77 | + ) |
| 78 | + }, |
| 79 | +}) |
48 | 80 |
|
49 | 81 | // --------- Preload scripts loading --------- |
50 | 82 | function domReady(condition = ["complete", "interactive"]) { |
|
0 commit comments