electron-dev
This skill provides patterns for building production-grade Electron applications using React and TypeScript, emphasizing security hardening through context isolation, sandboxing, and Electron Fuses with ASAR integrity validation. Use when developing desktop applications requiring IPC communication, window management, preload script configuration, or secure inter-process messaging between main and renderer processes.
git clone --depth 1 https://github.com/jamditis/claude-skills-journalism /tmp/electron-dev && cp -r /tmp/electron-dev/dev-toolkit/skills/electron-dev ~/.claude/skills/electron-devSKILL.md
# Electron desktop development
Patterns and practices for building production-quality Electron applications with React and TypeScript.
## Security baseline (Electron 30+)
Electron's defaults have hardened over the past several releases. As of Electron 28+, `contextIsolation: true` and `sandbox: true` are the defaults for new BrowserWindow instances, most security advice from older guides assumed you had to opt in. You don't anymore; you have to opt OUT, and you should not.
Set explicitly anyway, so a config drift never weakens the security model:
```javascript
const win = new BrowserWindow({
webPreferences: {
contextIsolation: true, // default since 12, mandatory for any prod app
sandbox: true, // default since 28; renderer runs sandboxed
nodeIntegration: false, // never enable in renderer
webSecurity: true, // never disable
preload: path.join(__dirname, 'preload.cjs')
}
});
```
Validate every IPC message in main. Don't trust the renderer.
### Electron Fuses + ASAR integrity
Electron Fuses are package-time toggles baked into the binary. The two relevant for security distribution:
- `EnableEmbeddedAsarIntegrityValidation`, verifies the app.asar hash at runtime against a hash embedded in the binary. Defends against attackers swapping the asar contents post-install.
- `OnlyLoadAppFromAsar`, refuses to load app code from anywhere except the validated asar.
These are **opt-in**, not default. Enable both for production. Requires `@electron/asar` 3.1.0+ to generate the asar with embeddable integrity. electron-builder configures this via `electronFuses` in the build config; `@electron/fuses` does it programmatically.
CVE-2023-44402 (ASAR integrity bypass via filetype confusion) was the canonical motivation here, without integrity + only-load-from-asar, an attacker who can modify app files can swap behavior silently.
### Common renderer-side risks
- **Preload script confusion**, only expose narrow, typed surfaces via `contextBridge.exposeInMainWorld`. Never re-export `ipcRenderer` itself; expose specific methods that map to specific channels.
- **`file://` IPC and navigation**, restrict navigation with `webContents.on('will-navigate', e => e.preventDefault())` for windows that shouldn't change URL. Deny `setWindowOpenHandler` requests by default; allow-list specific origins.
- **`shell.openExternal` with user input**, validate the URL scheme before opening. An attacker-controlled `file://` or `javascript:` URL hands them code execution.
## Architecture patterns
### Project structure
```
app/
├── electron/
│ ├── main.cjs # Main process (CommonJS required)
│ ├── preload.cjs # Context bridge for secure IPC
│ └── server.cjs # Optional: WebSocket/HTTP server
├── src/
│ ├── components/ # React components
│ ├── services/ # Business logic (API clients, Firebase)
│ ├── utils/ # Utilities (audio, formatting)
│ ├── types.ts # TypeScript interfaces
│ ├── App.tsx # Root component
│ └── index.tsx # React entry
├── assets/ # Icons, sounds, images
├── package.json
├── vite.config.ts
└── electron-builder.yml # Build configuration
```
### IPC communication pattern
**Main process (main.cjs):**
```javascript
const { ipcMain } = require('electron');
// Handle async requests from renderer
ipcMain.handle('action-name', async (event, args) => {
try {
const result = await someAsyncOperation(args);
return { success: true, data: result };
} catch (error) {
return { success: false, error: error.message };
}
});
// Send data to renderer
mainWindow.webContents.send('event-name', data);
```
**Preload script (preload.cjs):**
```javascript
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electron', {
actionName: (args) => ipcRenderer.invoke('action-name', args),
onEventName: (callback) => {
const handler = (event, data) => callback(data);
ipcRenderer.on('event-name', handler);
return () => ipcRenderer.removeListener('event-name', handler);
}
});
```
**Renderer (React):**
```typescript
const result = await window.electron.actionName(args);
useEffect(() => {
return window.electron.onEventName((data) => {
setState(data);
});
}, []);
```
## System tray integration
```javascript
const { Tray, Menu, nativeImage } = require('electron');
let tray = null;
function createTray() {
const icon = nativeImage.createFromPath(path.join(__dirname, '../assets/tray-icon.png'));
tray = new Tray(icon.resize({ width: 16, height: 16 }));
tray.setToolTip('App Name');
tray.setContextMenu(Menu.buildFromTemplate([
{ label: 'Show', click: () => mainWindow.show() },
{ label: 'Quit', click: () => app.quit() }
]));
tray.on('click', () => {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
});
}
// Hide to tray instead of closing
mainWindow.on('close', (event) => {
if (!app.isQuitting) {
event.preventDefault();
mainWindow.hide();
}
});
```
## Global shortcuts
```javascript
const { globalShortcut } = require('electron');
app.whenReady().then(() => {
// Register with conflict detection
const registered = globalShortcut.register('Alt+S', () => {
mainWindow.webContents.send('shortcut-triggered', 'toggle-recording');
});
if (!registered) {
console.error('Shortcut registration failed - conflict detected');
}
});
app.on('will-quit', () => {
globalShortcut.unregisterAll();
});
```
## PTY terminal integration (node-pty)
```javascript
const pty = require('node-pty');
const shell = process.platform === 'win32' ? 'powershell.exe' : process.env.SHELL || '/bin/bash';
const ptyProcess = pty.spawn(shell, [], {
name: 'xterm-256color',
cols: 80,
rows: 24,
cwd: process.env.HOME,
env: process.env
});
ptyProcess.onData((data) => {
mainWindow.webContents.senWeb accessibility patterns for news and academic sites. Use for WCAG audits, alt text, accessible data viz, and assistive tech.
Remote JavaScript console and debugging on mobile. Use for phone/tablet console errors, responsive testing, Eruda, and vConsole.
Flags irreversible decisions before commit. Use for data models, infra, auth boundaries, API contracts, event schemas, CI/CD.
Python data pipelines with modular architecture. Use for content workflows, batch jobs, or Google Sheets/Drive integration.
Enforces a test-driven bug-fixing workflow. Use when a user reports a bug, failing code, an error, or asks to fix something.
AI-assisted coding with Claude Code, Cursor, Copilot, Codex, Aider, or Windsurf. Use to build or debug generated code.
Authorized web scraping with fallback cascades and access-failure handling. Use for social media, yt-dlp, CAPTCHA or 403 blocks.
Signs of taste in web UI. Use when building or reviewing web interfaces, dashboards, SaaS apps, or internal tools.