Securing Electron Apps: A Practical Guide
Handling secrets and IPC securely in modern desktop apps.
Why Electron is a sharp tool
An Electron app is a browser and a Node.js runtime in one process. If a renderer that loads remote or attacker-influenced content can reach Node, a single XSS becomes remote code execution on the user's machine. Securing Electron is mostly about keeping those two worlds apart. GitSwitch, an Electron + React 19 Git client that holds GitHub tokens and a Gemini API key, has to get this right.
The isolation contract
| Setting | Value | Why |
|---|---|---|
contextIsolation | on | Preload and page run in separate JS contexts, so the page cannot tamper with privileged globals |
sandbox | on | Renderers get no direct Node.js, no require, no fs, no child_process |
nodeIntegration | off | The page never sees Node built-ins |
| Exposed API | contextBridge allow-list | The preload exposes a small named set of functions, not raw ipcRenderer |
IPC as a trust boundary
Every privileged action crosses ipcMain.handle / ipcRenderer.invoke on an explicit channel allow-list. The main process treats each message as untrusted input: channels are validated, arguments are sanitised, and long-running calls carry timeouts so a hung git operation cannot wedge the app. There is no "run arbitrary command" bridge; each channel does one narrow thing.
Handling secrets
Tokens never touch localStorage or linger in the renderer:
- ▹At rest, GitHub personal-access tokens and the Gemini key live in the OS keychain (Keychain on macOS, Credential Manager on Windows, libsecret on Linux), not a plaintext config file.
- ▹In use, keys are redacted from logs and wiped from memory after use, so a crash dump or shared log does not leak them.
- ▹In transit, the Gemini API is the only outbound egress, and the diff sent for commit-message generation is scoped to what the user is committing.
The takeaway
The defaults that make Electron convenient, Node in the renderer, the legacy remote module, broad IPC, are the ones that make it dangerous. GitSwitch inverts them: isolation on, sandbox on, a minimal audited bridge, and secrets in the OS keychain. The result is a desktop app with a web UI that cannot be talked into running code it was not built to run.