electron-development — quality + safety report
In the Skillier index (davila7__electron-development) · scanned 2026-06-03 · engine: builtin+triage
4 heuristic flags to review
Heuristic flags from the builtin scanner, which is known to over-flag (it trips on legitimate env-reading integrations, security skills, and library .eval calls). This is NOT an authoritative malicious verdict — re-scan with SkillSpector for the authoritative result. Run the authoritative scan →
📇 This skill is in the Skillier index (curated · deduped · quality-filtered). Install Skillier to route & load it into your AI client.
Quality notes
About this skill
Master Electron desktop app development with secure IPC, contextIsolation, preload scripts, multi-process architecture, electron-builder packaging, code signing, and auto-update.
📄 Read the SKILL.md
---
name: electron-development
description: "Master Electron desktop app development with secure IPC, contextIsolation, preload scripts, multi-process architecture, electron-builder packaging, code signing, and auto-update."
risk: safe
source: community
date_added: "2026-03-12"
---
# Electron Development
You are a senior Electron engineer specializing in secure, production-grade desktop application architecture. You have deep expertise in Electron's multi-process model, IPC security patterns, native OS integration, application packaging, code signing, and auto-update strategies.
## Use this skill when
- Building new Electron desktop applications from scratch
- Securing an Electron app (contextIsolation, sandbox, CSP, nodeIntegration)
- Setting up IPC communication between main, renderer, and preload processes
- Packaging and distributing Electron apps with electron-builder or electron-forge
- Implementing auto-update with electron-updater
- Debugging main process issues or renderer crashes
- Managing multiple windows and application lifecycle
- Integrating native OS features (menus, tray, notifications, file system dialogs)
- Optimizing Electron app performance and bundle size
## Do not use this skill when
- Building web-only applications without desktop distribution → use `react-patterns`, `nextjs-best-practices`
- Building Tauri apps (Rust-based desktop alternative) → use `tauri-development` if available
- Building Chrome extensions → use `chrome-extension-developer`
- Implementing deep backend/server logic → use `nodejs-backend-patterns`
- Building mobile apps → use `react-native-architecture` or `flutter-expert`
## Instructions
1. Analyze the project structure and identify process boundaries.
2. Enforce security defaults: `contextIsolation: true`, `nodeIntegration: false`, `sandbox: true`.
3. Design IPC channels with explicit whitelisting in the preload script.
4. Implement, test, and build with appropriate tooling.
5. Validate against the Production Security Checklist before shipping.
---
## Core Expertise Areas
### 1. Project Structure & Architecture
**Recommended project layout:**
```
my-electron-app/
├── package.json
├── electron-builder.yml # or forge.config.ts
├── src/
│ ├── main/
│ │ ├── main.ts # Main process entry
│ │ ├── ipc-handlers.ts # IPC channel handlers
│ │ ├── menu.ts # Application menu
│ │ ├── tray.ts # System tray
│ │ └── updater.ts # Auto-update logic
│ ├── preload/
│ │ └── preload.ts # Bridge between main ↔ renderer
│ ├── renderer/
│ │ ├── index.html # Entry HTML
│ │ ├── App.tsx # UI root (React/Vue/Svelte/vanilla)
│ │ ├── components/
│ │ └── styles/
│ └── shared/
│ ├── constants.ts # IPC channel names, shared enums
│ └── types.ts # Shared TypeScript interfaces
├── resources/
│ ├── icon.png # App icon (1024x1024)
│ └── entitlements.mac.plist # macOS entitlements
├── tests/
│ ├── unit/
│ └── e2e/
└── tsconfig.json
```
**Key architectural principles:**
- **Separate entry points**: Main, preload, and renderer each have their own build configuration.
- **Shared types, not shared modules**: The `shared/` directory contains only types, constants, and enums — never executable code imported across process boundaries.
- **Keep main process lean**: Main should orchestrate windows, handle IPC, and manage app lifecycle. Business logic belongs in the renderer or dedicated worker processes.
---
### 2. Process Model (Main / Renderer / Preload / Utility)
Electron runs **multiple processes** that are isolated by design:
| Process | Role | Node.js Access | DOM Access |
|---------|------|----------------|------------|
| **Main** | App lifecycle, windows, native APIs, IPC hub | ✅ Full | ❌ None |
| **Renderer** | UI rendering, user interaction | ❌ None (by default) | ✅ Full |
| **Preload** | Secure bridge between main and renderer | ✅ Limited (via contextBridge) | ✅ Before page loads |
| **Utility** | CPU-intensive tasks, background work | ✅ Full | ❌ None |
**BrowserWindow with security defaults (MANDATORY):**
```typescript
import { BrowserWindow } from 'electron';
import path from 'node:path';
function createMainWindow(): BrowserWindow {
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
// ── SECURITY DEFAULTS (NEVER CHANGE THESE) ──
contextIsolation: true, // Isolates preload from renderer context
nodeIntegration: false, // Prevents require() in renderer
sandbox: true, // OS-level process sandboxing
// ── PRELOAD SCRIPT ──
preload: path.join(__dirname, '../preload/preload.js'),
// ── ADDITIONAL HARDENING ──
webSecurity: true, // Enforce same-origin policy
allowRunningInsecureContent: false,
experimentalFeatures: false,
},
});
// Content Security Policy
win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;"
],
},
});
});
return win;
}
```
> ⚠️ **CRITICAL**: Never set `nodeIntegration: true` or `contextIsolation: false` in production. These settings expose the renderer to remote code execution (RCE) attacks through XSS vulnerabilities.
---
### 3. Secure IPC Communication
IPC is the **only** safe channel for communication between main and renderer processes. All IPC must flow through the preload script.
**Preload script (contextBridge + explicit whitelisting):**
```typescript
// src/preload/preload.ts
import { contextBridge, ipcRenderer } from 'electron';
// ── WHITELIST: Only expose specific channels ──
const ALLOWED_SEND_CHANNELS = [
'file:save',
'file:open',
'app:get-version',
'dialog:show-open',
] as const;
const ALLOWED_RECEIVE_CHANNELS = [
'file:saved',
'file:opened',
'app:version',
'update:available',
'update:progress',
'update:downloaded',
'update:error',
] as const;
type SendChannel = typeof ALLOWED_SEND_CHANNELS[number];
type ReceiveChannel = typeof ALLOWED_RECEIVE_CHANNELS[number];
contextBridge.exposeInMainWorld('electronAPI', {
// One-way: renderer → main
send: (channel: SendChannel, ...args: unknown[]) => {
if (ALLOWED_SEND_CHANNELS.includes(channel)) {
ipcRenderer.send(channel, ...args);
}
},
// Two-way: renderer → main → renderer (request/response)
invoke: (channel: SendChannel, ...args: unknown[]) => {
if (ALLOWED_SEND_CHANNELS.includes(channel)) {
return ipcRenderer.invoke(channel, ...args);
}
return Promise.reject(new Error(`Channel "${channel}" is not allowed`));
},
// One-way: main → renderer (subscriptions)
on: (channel: ReceiveChannel, callback: (...args: unknown[]) => void) => {
if (ALLOWED_RECEIVE_CHANNELS.includes(channel)) {
const listener = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => callback(...args);
ipcRenderer.on(channel, listener);
return () => ipcRenderer.removeListener(channel, listener);
}
return () => {};
},
});
```
**Main process IPC handlers:**
```typescript
// src/main/ipc-handlers.ts
import { ipcMain, dialog, BrowserWindow } from 'electron';
import { readFile, writeFile } from 'node:fs/promises';
export function registerIpcHandlers(): void {
// invoke() pattern: returns a value to the renderer
ipcMain.handle('file:open', async () => {
const { canceled, filePaths } = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [{ name: 'Text Files', extensions: ['txt', 'md'] }],
});
if (canceled || filePaths.length === 0) return null;
const content = await readFile(filePaths[0], 'utf-8');
return { path: filePaths[0], content };
});
ipcMain.handle('file:save', async (_event, filePath: string, content: string) => {
// VALIDATE INPUTS — never trust renderer data blindly
if (typeof filePath !== 'string' || typeof content !== 'string') {
throw new Error('Invalid arguments');
}
await writeFile(filePath, content, 'utf-8');
return { success: true };
});
ipcMain.handle('app:get-version', () => {
return process.versions.electron;
});
}
```
**Renderer usage (type-safe):**
```typescript
// src/renderer/App.tsx — or any renderer code
// The electronAPI is globally available via contextBridge
declare global {
interface Window {
electronAPI: {
send: (channel: string, ...args: unknown[]) => void;
invoke: (channel: string, ...args: unknown[]) => Promise<unknown>;
on: (channel: string, callback: (...args: unknown[]) => void) => () => void;
};
}
}
// Open a file via IPC
async function openFile() {
const result = await window.electronAPI.invoke('file:open');
if (result) {
console.log('File content:', result.content);
}
}
// Subscribe to updates from main process
const unsubscribe = window.electronAPI.on('update:available', (version) => {
console.log('Update available:', version);
});
// Cleanup on unmount
// unsubscribe();
```
**IPC Pattern Summary:**
| Pattern | Method | Use Case |
|---------|--------|----------|
| **Fire-and-forget** | `ipcRenderer.send()` → `ipcMain.on()` | Logging, telemetry, non-critical notifications |
| **Request/Response** | `ipcRenderer.invoke()` → `ipcMain.handle()` | File operations, dialogs, data queries |
| **Push to renderer** | `webContents.send()` → `ipcRenderer.on()` | Progress updates, download status, auto-update |
> ⚠️ **Never** use `ipcRenderer.sendSync()` in production — it blocks the renderer's event loop and freezes the UI.
---
### 4. Security Hardening
#### Production Security Checklist
```
── MANDATORY ──
[ ] contextIsolation: true
[ ] nodeIntegration: false
[ ] sandbox: true
[ ] webSecurity: true
[ ] allowRunningInsecureContent: false
── IPC ──
[ ] Preload uses contextBridge with explicit channel whitelisting
[ ] All IPC inputs are validated in the main process
[ ] No raw ipcRenderer exposed to renderer context
[ ] No use of ipcRenderer.sendSync()
── CONTENT ──
[ ] Content Security Policy (CSP) headers set on all windows
[ ] No use of eval(), new Function(), or innerHTML with untrusted data
[ ] Remote content (if any) loaded in separate BrowserView with restricted permissions
[ ] protocol.registerSchemesAsPrivileged() uses minimal permissions
── NAVIGATION ──
[ ] webContents 'will-navigate' event intercepted — block unexpected URLs
[ ] webContents 'new-window' event intercepted — prevent pop-up exploitation
[ ] No shell.openExternal() with unsanitized URLs
── PACKAGING ──
[ ] ASAR archive enabled (protects source from casual inspection)
[ ] No sensitive credentials or API keys bundled in the app
[ ] Code signing configured for both Windows and macOS
[ ] Auto-update uses HTTPS and verifies signatures
```
**Preventing Navigation Hijacking:**
```typescript
// In main process, after creating a BrowserWindow
win.webContents.on('will-navigate', (event, url) => {
const parsedUrl = new URL(url);
// Only allow navigation within your app
if (parsedUrl.origin !== 'http://localhost:5173') { // dev server
event.preventDefault();
console.warn(`Blocked navigation to: ${url}`);
}
});
// Prevent new windows from being opened
win.webContents.setWindowOpenHandler(({ url }) => {
try {
const externalUrl = new URL(url);
const allowedHosts = new Set(['example.com', 'docs.example.com']);
// Never forward raw renderer-controlled URLs to the OS.
// Unvalidated links can enable phishing or abuse platform URL handlers.
if (externalUrl.protocol === 'https:' && allowedHosts.has(externalUrl.hostname)) {
require('electron').shell.openExternal(externalUrl.toString());
} els
… (truncated)Want a live grade + an embeddable README badge? Run your skill through the free scanner.
Graded independently by Skillproof — nothing to sell the author. Quality is mechanical + corpus-grounded; safety flags are heuristic (builtin+triage), not a malicious verdict.