Electron contextIsolation + Preload Security Pattern

Problem

With nodeIntegration: true, the renderer (webpage) can directly access the filesystem via require('fs'). If loading external URLs, this is a critical security risk.

Solution

// main.js
const mainWindow = new BrowserWindow({
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
    contextIsolation: true,   // separate renderer and Node.js contexts
    nodeIntegration: false,   // block require in renderer
  }
});
// preload.js - bridge between main and renderer
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  scanNetwork: () => ipcRenderer.invoke('scan-network'),
  getVersion: () => ipcRenderer.invoke('get-version'),
  onScanResult: (callback) =>
    ipcRenderer.on('scan-result', (_, data) => callback(data)),
});
// renderer.js - use in the webpage
const results = await window.electronAPI.scanNetwork();

Key Points

  • With contextIsolation: true, the preload script’s globals and the renderer’s globals are completely separate. Only functions exposed via contextBridge.exposeInMainWorld are accessible.
  • The renderer can only use what’s defined in window.electronAPI. Dangerous access like require('child_process') is impossible.
  • ipcRenderer.invoke returns a Promise for bidirectional communication. Handle it in the main process with ipcMain.handle.