Skip to main content
rule

electron — Cursor rule: secure Electron best practices

A Cursor rule that pins down how your team writes Electron code: context isolation, safe contextBridge APIs, request-response IPC and cross-platform paths.

Install into Cursor · Manually: put the files in place All methods and platforms ↓
.cursor/rules/electron.mdc
Open on GitHub

What electron does

The rule attaches to Cursor and applies to **/*.{js,jsx} files. It assumes Electron 28 or newer, Electron Forge for scaffolding, and a Vite + TypeScript template. File names use hyphens, not underscores.

Security is stated as a requirement, not a suggestion. contextIsolation stays on, sandbox: true is recommended, and anything exposed to the renderer goes through contextBridge.exposeInMainWorld as a named function with argument checks. Handing ipcRenderer.send straight to the window is called out as a mistake, since the renderer can then fire arbitrary IPC messages. For calls that need an answer, the rule wants ipcMain.handle plus ipcRenderer.invoke instead of the send/on pair, which is prone to races.

The rest covers details that tend to slip: a strict CSP in index.html, .mjs extensions for ESM in main and preload scripts, awaiting imports before app.whenReady(), and path.join or os.tmpdir() rather than string concatenation. Linting and tests live in package.json scripts and run from a pre-commit hook. One catch worth knowing up front: sandboxed preload scripts cannot use ESM imports, so external modules have to be bundled.

What it helps with

  • stop the renderer from sending arbitrary IPC messages
  • remove race conditions in renderer-to-main requests
  • build file paths that work on Windows and macOS alike
  • add a strict CSP without breaking script loading
  • move a project to ESM on Electron 28+

Example prompts for the agent

  • Check the preload script — are we exposing more than we should through contextBridge?
  • Rewrite this data request using ipcMain.handle and ipcRenderer.invoke
  • Replace the string concatenation with path.join and os.tmpdir()

How to install electron

Manually: put the files in place

.cursor/rules/electron.mdc

Frontmatter: description, globs, alwaysApply

Step by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules

Questions about electron

How do I install the electron rule in Cursor?

Create .cursor/rules/electron.mdc at the root of your project and paste the rule contents into it.

What does the electron rule do?

It sets requirements for Electron code: context isolation, safe APIs through contextBridge, request-response IPC, cross-platform paths and one consistent build toolchain.

Which files does the rule apply to?

JavaScript and JSX files matching **/*.{js,jsx}.

Source text

electron.mdc · CC0-1.0
---
description: This guide provides opinionated, actionable best practices for building secure, performant, and maintainable Electron applications using modern patterns and consistent tooling.
globs: **/*.{js,jsx}
---
# Electron Best Practices

Electron development demands a disciplined approach to security, performance, and maintainability. This guide outlines the definitive best practices for our team, leveraging modern Electron features and tooling (targeting Electron 28+).

## 1. Project Setup & Structure

Always start with **Electron Forge** to standardize project structure, build pipelines, and stay aligned with the latest Electron APIs.

-   **Scaffolding:** Use a modern template like Vite + TypeScript.
    ❌ BAD: Manual setup, outdated CLIs.
    ✅ GOOD:
    ```bash
    npx create-electron-app@latest my-app --template=vite-typescript
    ```

-   **File Naming:** Adhere to Electron's coding style for JavaScript files.
    ❌ BAD: `my_module.js`
    ✅ GOOD: `my-module.js`

## 2. Security Fundamentals (Non-Negotiable)

Security is paramount. Always enable context isolation and expose APIs safely.

-   **Context Isolation (Mandatory):** Keep `contextIsolation` enabled. It's on by default since Electron 12.
    ❌ BAD: `new BrowserWindow({ webPreferences: { contextIsolation: false } })`
    ✅ GOOD: (Default behavior, no explicit setting needed unless overriding)
    ```javascript
    // main.mjs
    new BrowserWindow({
      webPreferences: {
        preload: path.join(__dirname, 'preload.mjs'),
        sandbox: true // Strongly recommended
      }
    })
    ```

-   **Safe API Exposure with `contextBridge`:** Never mutate the global `window` object directly. Use `contextBridge.exposeInMainWorld` and filter arguments.
    ❌ BAD: Exposing `ipcRenderer.send` directly.
    ```javascript
    // preload.mjs
    const { contextBridge, ipcRenderer } = require('electron');
    contextBridge.exposeInMainWorld('myAPI', {
      send: ipcRenderer.send // ❌ Allows renderer to send arbitrary IPC messages
    });
    ```
    ✅ GOOD: Expose specific, argument-filtered functions.
    ```javascript
    // preload.mjs
    import { contextBridge, ipcRenderer } from 'electron';

    contextBridge.exposeInMainWorld('electronAPI', {
      loadPreferences: () => ipcRenderer.invoke('load-prefs'),
      saveSettings: (settings) => {
        // ✅ Validate and filter arguments before sending
        if (typeof settings === 'object' && settings !== null) {
          ipcRenderer.send('save-settings', settings);
        } else {
          console.error('Invalid settings object provided.');
        }
      }
    });

    // interface.d.ts (for TypeScript)
    declare global {
      interface Window {
        electronAPI: {
          loadPreferences: () => Promise<any>;
          saveSettings: (settings: object) => void;
        };
      }
    }
    ```

-   **Content Security Policy (CSP):** Implement a strict CSP in your `index.html` or via `webRequest.onHeadersReceived`.
    ✅ GOOD:
    ```html
    <!-- index.html -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'">
    ```

## 3. ES Modules (ESM) Adoption (Electron 28+)

Leverage native ESM for cleaner, more modern code.

-   **Main Process:** Use `.mjs` extension or `"type": "module"` in `package.json`.
    -   **`await` for Pre-Ready APIs:** ESM imports are asynchronous. Ensure critical APIs (e.g., `app.setPath`) are `await`ed before `app.whenReady()`.
    ❌ BAD:
    ```javascript
    // main.mjs
    import './setup-paths.mjs'; // May resolve after app is ready
    app.whenReady().then(() => { /* ... */ });
    ```
    ✅ GOOD:
    ```javascript
    // main.mjs
    import { app } from 'electron';
    await import('./setup-paths.mjs'); // Guarantees execution before ready
    app.whenReady().then(() => { /* ... */ });
    ```

-   **Preload Scripts:** Always use the `.mjs` extension for ESM preload scripts.
    -   **Sandboxed Preloads:** Cannot use ESM imports. Bundle them if external modules are needed.
    -   **Context Isolation:** Required for dynamic Node.js ESM imports in unsandboxed preloads.
    ❌ BAD: `preload.js` with `import` statements.
    ✅ GOOD: `preload.mjs`

## 4. IPC Communication

Use `ipcMain.handle` and `ipcRenderer.invoke` for explicit request-response patterns.

-   **Request-Response:**
    ❌ BAD: Using `ipcRenderer.send` for requests that expect a response.
    ```javascript
    // renderer.js
    ipcRenderer.send('get-data', someId);
    ipcRenderer.on('data-response', (event, data) => { /* ... */ }); // Race condition prone
    ```
    ✅ GOOD:
    ```javascript
    // main.mjs
    ipcMain.handle('get-data', async (event, someId) => {
      // ✅ Perform validation, access native APIs
      return await fetchData(someId);
    });

    // renderer.js
    const data = await window.electronAPI.getData(someId); // Assuming exposed via contextBridge
    ```

## 5. System Path Handling

Always use Node.js `path` and `os` modules for cross-platform compatibility.

-   **File Paths:** Use `path.join()` for concatenation.
    ❌ BAD: `app.getPath('userData') + '/config.json'`
    ✅ GOOD:
    ```javascript
    import path from 'node:path';
    import { app } from 'electron';
    const configPath = path.join(app.getPath('userData'), 'config.json');
    ```

-   **Temporary Directories:** Use `os.tmpdir()`.
    ❌ BAD: `'/tmp/my-app-data'`
    ✅ GOOD:
    ```javascript
    import os from 'node:os';
    const tempDir = os.tmpdir();
    ```

## 6. Testing & Linting

Integrate Electron's built-in tooling for consistent code quality.

-   **Linting:** Run `npm run lint` regularly and integrate into pre-commit hooks.
    ✅ GOOD: Ensure your `package.json` includes:
    ```json
    "scripts": {
      "lint": "electron-builder lint" // Or specific linter like 'eslint .'
    }
    ```

-   **Unit Tests:** Add new tests for any changes or new features.
    ✅ GOOD: `npm run test`
    ```json
    "scripts": {
      "test": "electron-mocha spec" // Example with electron-mocha
    }
    ```

## 7. Staying Current

Electron evolves rapidly. Proactively manage updates and breaking changes.

-   **Official Documentation:** Always consult the version-specific official documentation.
-   **Breaking Changes:** Regularly review the "Breaking Changes" page for each major Electron release to anticipate necessary updates.

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Similar IDE rules

Everything for Security →

From the same set69

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor