Electron Process Security: Minimal Design for Safe Renderer Isolation
Keep Electron renderers unprivileged by using contextIsolation, a preload bridge, and validated IPC. This guide shows the smallest suitable architecture, trust boundaries, operational checks, and when to adjust the design.
31 May 2026, 08:56 UTC

Problem & Takeaway
Electron bundles Chromium and Node.js, giving every window full OS access if misconfigured. The goal is to keep the renderer – the code that runs inside a BrowserWindow – as unprivileged as a normal web page, while still allowing the app to perform native work. The smallest, safest design uses contextIsolation: true, a preload script that exposes a narrow typed API via contextBridge, and validated IPC channels that route privileged operations through the main process.
Requirements
- Electron v26+ (or the version you pin).
- All BrowserWindow instances must set
nodeIntegration: false(default) andwebSecurity: true. - Enable
contextIsolation: trueto prevent renderer code from accessing the main world. - Provide a
preloadscript that only exposes required APIs viacontextBridge.exposeInMainWorld. - All privileged work (file system, shell, auto‑update, native dialogs) must be handled in the main process via
ipcMain.handleoripcMain.on. - Validate every IPC payload in the main process (schema checks, allow‑lists, origin verification).
Minimal Suitable Design
1. One main process – runs the app's core logic, manages windows, and owns all Node.js APIs.
2. One renderer per window – Chromium sandbox, no Node.js access.
3. Preload bridge – a small script that runs in the renderer’s isolated world, exposing a typed API.
4. Explicit IPC channels – ipcRenderer.invoke('save-file', data) → ipcMain.handle('save-file', handler).
Trust & Data Boundaries
- Renderer never touches Node.js – attempts to
require('fs')will throw in production. - Preload is the only boundary – it can expose native APIs but must be written to avoid privilege creep.
- IPC is the trust boundary – the main process must treat all messages as untrusted, validate, and reject unexpected senders.
- Origin checks – when using
webviewor loading remote URLs, verifyevent.senderFrame.originbefore acting.
Operational Checks
- Enumerate IPC channels – list all
ipcMain.handleandipcMain.onregistrations in a single review file. - Validate payloads – use JSON schema or TypeScript type guards in the main process.
- Smoke test renderer isolation – in a built app, open DevTools and confirm
require,process, and Node globals are undefined. - Origin enforcement – for
webviewor remote content, add a guard:ipcMain.handle('open-external', (event, url) => { if (!url.startsWith('https://trusted.com')) throw new Error('Untrusted URL'); shell.openExternal(url); }); - Crash resilience – monitor the main process; a crash brings down the entire app, so consider a watchdog or restart strategy for critical utilities.
Failure Modes & Mitigations
- Renderer compromise via XSS – mitigated by
nodeIntegration: falseandcontextIsolation: true. If an attacker injects code, they cannot access Node.js APIs directly. - Overly broad IPC handlers – avoid generic channels like
'run-command'. Instead, expose specific, narrowly scoped handlers. - Privilege creep in preload – keep the preload script lean; only expose functions that the renderer truly needs.
- Main‑process crash – can terminate the whole app. Use
app.on('unresponsive')andapp.on('crashed')to alert users and attempt graceful restarts.
When the Design Should Change
- Only local, fully trusted content – you may consider disabling
webSecurityor enablingnodeIntegrationfor performance, but this removes the sandbox and should be documented as a security trade‑off. - Loading third‑party web content – separate the content into a dedicated
BrowserVieworwebviewwith its own sandbox, and route any required IPC through a utility process. - Heavy native work that blocks the main thread – extract that work into a utility process and communicate via IPC to keep the UI responsive.
Concrete Example: Secure File Save
Below is a minimal, verifiable snippet that demonstrates the pattern.
main.js
const {app, BrowserWindow, ipcMain, dialog} = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
webSecurity: true,
}
});
win.loadFile('index.html');
}
app.whenReady().then(createWindow);
ipcMain.handle('save-file', async (event, {fileName, data}) => {
// Basic validation
if (typeof fileName !== 'string' || typeof data !== 'string') {
throw new Error('Invalid payload');
}
// Restrict to user data directory
const safePath = path.join(app.getPath('userData'), fileName);
await fs.promises.writeFile(safePath, data, 'utf8');
return {path: safePath};
});
preload.js
const {contextBridge, ipcRenderer} = require('electron');
contextBridge.exposeInMainWorld('api', {
saveFile: (fileName, data) => ipcRenderer.invoke('save-file', {fileName, data})
});
renderer (index.html)
<script>
async function onSave() {
const result = await window.api.saveFile('notes.txt', 'Hello world');
console.log('Saved to', result.path);
}
</script>
<button onclick="onSave()">Save</button>
Validation Checklist
| Item | Check |
|---|---|
All BrowserWindow instances have contextIsolation: true | Review webPreferences in code. |
| Preload only exposes required APIs | Code review of contextBridge.exposeInMainWorld. |
| IPC handlers validate payloads | Unit tests that send malformed data and expect rejection. |
| Renderer cannot require Node modules | Open DevTools, run require('fs'), confirm error. |
| Origin checks for remote content | Test event.senderFrame.origin in handlers. |
Conclusion
By keeping the renderer sandboxed, exposing a minimal API via preload, and routing privileged work through validated IPC, you achieve the smallest, most secure Electron architecture. Regular operational checks and a clear failure‑mode plan ensure that even if a renderer is compromised, the main process remains protected, and the user’s system stays safe.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.