Why Electron Needs Security-First Rules
Electron apps run a Chromium browser with Node.js access — the most powerful and most dangerous combination in desktop software. If nodeIntegration is enabled in the renderer (the default in old tutorials), any XSS vulnerability gives the attacker full system access: file system, network, child processes, everything. AI generates Electron code from outdated tutorials that enable nodeIntegration, skip contextIsolation, and expose the entire Node API to the renderer.
The modern Electron security model separates main (Node.js, full system access) from renderer (browser, sandboxed). Communication happens through IPC. The renderer only accesses system capabilities through a controlled API exposed via contextBridge. AI must generate code that follows this model — not the insecure shortcuts from 2018 tutorials.
These rules target Electron 28+ with the recommended security configuration. Every rule exists to prevent a specific class of security vulnerability.
Rule 1: Main Process vs Renderer Process
The rule: 'The main process (main.js/main.ts) has full Node.js access — file system, child processes, native modules. The renderer process (loaded HTML/JS) runs in a sandboxed Chromium browser — no Node.js access. All communication between them uses IPC. Never enable nodeIntegration in the renderer. Never disable contextIsolation. Never disable the sandbox.'
For BrowserWindow config: 'webPreferences: { nodeIntegration: false, contextIsolation: true, sandbox: true, preload: path.join(__dirname, "preload.js") }. These four settings are non-negotiable. nodeIntegration: false prevents Node access in the renderer. contextIsolation: true prevents prototype pollution. sandbox: true adds OS-level process isolation. preload: specifies the bridge script.'
AI generates BrowserWindow without webPreferences (insecure defaults in older Electron) or with nodeIntegration: true (every tutorial from 2018-2020 does this). One security config block prevents the entire class of Electron privilege escalation vulnerabilities.
- Main process: Node.js, file system, native modules, system access
- Renderer: sandboxed browser — no Node, no fs, no child_process
- nodeIntegration: false — always, no exceptions
- contextIsolation: true — prevents prototype pollution attacks
- sandbox: true — OS-level process isolation
nodeIntegration: false, contextIsolation: true, sandbox: true, preload: set. AI generates BrowserWindow without webPreferences — or with nodeIntegration: true from 2018 tutorials. These four settings prevent the entire class of Electron privilege escalation.
Rule 2: IPC for All Cross-Process Communication
The rule: 'Use ipcMain.handle + ipcRenderer.invoke for request/response IPC (async, return values). Use ipcMain.on + event.reply for fire-and-forget messages. Use contextBridge.exposeInMainWorld in the preload script to create a typed API that the renderer can call. Never use remote module — it's deprecated and insecure. Never send entire objects — send only the data needed.'
For the preload script: 'preload.js bridges main and renderer: contextBridge.exposeInMainWorld("api", { readFile: (path) => ipcRenderer.invoke("read-file", path), saveFile: (path, content) => ipcRenderer.invoke("save-file", path, content) }). The renderer accesses window.api.readFile() — a controlled, typed interface. Never expose ipcRenderer directly — wrap every call in a named function.'
For main handlers: 'Register handlers with ipcMain.handle: ipcMain.handle("read-file", async (event, filePath) => { // Validate filePath — never trust renderer input return fs.readFile(filePath, "utf8"); }). Validate all inputs from the renderer — it's an untrusted source. Never pass paths directly to fs without validation.'
contextBridge.exposeInMainWorld('api', { readFile: (path) => ipcRenderer.invoke('read-file', path) }). Wrap every IPC call in a named function. The renderer sees window.api — a controlled, typed interface. Never expose raw ipcRenderer.
Rule 3: Security Hardening
The rule: 'Set a restrictive Content-Security-Policy: session.defaultSession.webRequest.onHeadersReceived((details, callback) => { callback({ responseHeaders: { ...details.responseHeaders, "Content-Security-Policy": ["default-src 'self'; script-src 'self'"] } }) }). Disable navigation to external URLs: win.webContents.on("will-navigate", (event) => event.preventDefault()). Disable new window creation: app.on("web-contents-created", (event, contents) => { contents.setWindowOpenHandler(() => ({ action: "deny" })) }).'
For external content: 'Never load remote URLs in the main window — only local files (file:// or custom protocol). If you must display external content, use a <webview> tag with sandboxed permissions or open in the system browser: shell.openExternal(url). Validate all URLs before opening: only allow https:// with known domains.'
For updates: 'Use electron-updater for auto-updates. Sign the application with a code signing certificate. Verify update signatures before applying. Use HTTPS for update servers. Never download and execute unsigned code — this is the most common Electron supply chain attack vector.'
- Content-Security-Policy: default-src 'self' — no inline scripts
- Prevent navigation to external URLs — win.webContents will-navigate
- Deny new window creation — setWindowOpenHandler returns deny
- Never load remote URLs in main window — local files only
- Code signing + signed updates — electron-updater with HTTPS
Validate ALL inputs from the renderer in ipcMain handlers — the renderer is an untrusted source (like a browser client). Never pass renderer-supplied paths directly to fs. An XSS in the renderer becomes a local file read without validation.
Rule 4: Application Architecture
The rule: 'Separate concerns across processes: main process handles file system, databases (SQLite via better-sqlite3), system tray, native menus, and auto-updates. Renderer handles UI (React, Vue, Svelte, or vanilla). Preload script exposes a typed API bridge. Use TypeScript in all three layers for type safety across the IPC boundary.'
For data persistence: 'Use better-sqlite3 or electron-store for local data. Database operations happen in the main process — the renderer requests data via IPC. For complex data, consider SQLite with Drizzle or Prisma (configured for SQLite). Never store sensitive data unencrypted — use safeStorage.encryptString() for credentials.'
For the build pipeline: 'Use electron-forge or electron-builder for packaging. Configure for all target platforms: Windows (NSIS/MSI), macOS (DMG), Linux (AppImage/deb/rpm). Use asar packaging for source code. Exclude development dependencies from the build. Set proper application metadata: name, version, author, description.'
Rule 5: Testing Electron Applications
The rule: 'Use Playwright or Spectron for end-to-end testing — they drive the actual Electron window. Test IPC channels: verify that renderer calls produce correct main process behavior. Unit test main process logic independently (it's just Node.js). Unit test renderer logic with standard web testing tools (Vitest, Testing Library). Test the preload API contract: verify exposed functions match expectations.'
For E2E testing: 'Playwright supports Electron natively: const electronApp = await electron.launch({ args: ["."] }); const page = await electronApp.firstWindow();. Interact with the UI: page.click, page.fill. Verify results: expect(await page.textContent(".status")).toBe("Saved"). Close: await electronApp.close().'
For CI: 'Electron tests need a display — use xvfb-run on Linux CI: xvfb-run --auto-servernum npx playwright test. On macOS CI, no display setup needed. On Windows CI, tests run natively. Package and test the built app, not just the dev version — build artifacts can differ.'
Complete Electron Rules Template
Consolidated rules for Electron desktop applications.
- nodeIntegration: false, contextIsolation: true, sandbox: true — non-negotiable
- contextBridge + preload for typed IPC API — never expose ipcRenderer directly
- ipcMain.handle for request/response — validate all inputs from renderer
- CSP: default-src 'self' — prevent navigation — deny new windows — local files only
- Main: file system, SQLite, native menus — Renderer: UI only, sandboxed
- safeStorage for credentials — electron-updater with code signing
- electron-forge or electron-builder — asar packaging — all platform targets
- Playwright for E2E — Vitest for unit — xvfb-run for Linux CI