In an Electron preload script, expose one small function per operation, such as loadPreferences, and never hand the page ipcRenderer.send or a similar generic call. Context isolation only creates the boundary. What you place on the bridge decides whether that boundary protects anything.
What the bridge does
With context isolation on, your preload runs in a separate JavaScript context from the page. The page cannot read what the preload put on its own window object. To share an API you call contextBridge.exposeInMainWorld, and the page then sees only what you exposed.
That is a good design, and it is easy to undo with one line.
The unsafe pattern
The Electron docs show the mistake plainly. Passing ipcRenderer.send straight through gives any page the ability to pick the channel and the payload. Any website your window loads, or any injected script, can then send arbitrary IPC messages to the main process.
// Unsafe: the page chooses channel and arguments
contextBridge.exposeInMainWorld('myAPI', {
send: ipcRenderer.send
})
The safe pattern
Expose a fixed operation for each message you intend to support.
// Safer: one fixed operation
contextBridge.exposeInMainWorld('electronAPI', {
loadPreferences: () => ipcRenderer.invoke('load-prefs')
})
The channel name lives in the preload, not in the page. The page cannot invent new ones.
Do not leak the event object
The security guide also warns about listeners. If you expose a function that registers a callback straight onto ipcRenderer.on, the callback receives the event, including sender details. Wrap the callback so that only the value is passed through.
Validate on the main side as well
The preload is a convenience, not a gate. Treat every message arriving in the main process as untrusted. Check the shape of the arguments, check where the message came from, and keep the handler narrow. The related post on validating IPC senders covers the origin check.
Review checklist
- Every exposed function maps to one named operation.
- No function accepts a channel name from the page.
- No function takes a path, command or URL and passes it on unchecked.
- Callbacks do not expose the IPC event object.
- Types are simple values. The bridge cannot carry custom prototypes or symbols anyway.
If your preload has more than a dozen exposed functions, ask whether the renderer really needs all of them. Each one is an entry point into the privileged side of the app.
Quick check
Is it safe to expose a function that reads any file path the page asks for?
No. Even a narrow looking name becomes a file read primitive if the argument is not restricted. Accept an identifier for a known file, and resolve the path on the main side.
Sources
Last reviewed: 19 October 2026.