[← Back to Root](../../../README.md) # IPC Architecture & Security Model The Secure Communication Blueprint. > This document defines the **Hardened Security Perimeter** and communication topology governing the Desktop Wrapper. Every native capability exposed to the renderer is mediated through a **Non-Bypassable IPC Bridge**, ensuring that the Node.js Main Process remains cryptographically and logically isolated from untrusted web content. Adherence to this document is **mandatory** — deviations constitute security violations subject to immediate remediation. --- ## Table of Contents - [Privilege Separation Model](#privilege-separation-model) - [Standard Operating Procedure: The Three-Step Bridge](#standard-operating-procedure-the-three-step-bridge) - [Verified Channel Manifest](#verified-channel-manifest) - [Extending the Bridge: Guided Walkthrough](#extending-the-bridge-guided-walkthrough) - [Critical Audit Checklist: Anti-Patterns](#critical-audit-checklist-anti-patterns) - [The Gold Standard for Native Integration](#the-gold-standard-for-native-integration) --- ## Privilege Separation Model The desktop wrapper enforces a **strict privilege separation** between three execution contexts, each operating under fundamentally different trust levels. This architecture ensures that a compromise in any single layer cannot escalate to full system access. ### Trust Level Matrix | Context | Trust Level | Privilege Scope | Security Guarantee | | ------------------ | ------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | **Main Process** | Fully Trusted | Unrestricted Node.js access: filesystem, network, printers, OS APIs, child processes | Only code authored by the engineering team executes here | | **Preload Script** | Controlled | Restricted to `ipcRenderer.invoke()` and `ipcRenderer.send()` — no direct Node.js access | Executes in a **Hermetically Sealed Context** — isolated from both the Main Process globals and the Renderer's DOM | | **Renderer** | Untrusted | Standard browser sandbox — zero Node.js API surface | Designated as a **Zero-Trust Environment** — may execute third-party code, npm packages, or XSS payloads | ### Process Topology ```mermaid graph TD %% ─── Styling Definitions (Dark-Mode Friendly Enterprise Palette) ─── classDef trustedLayer fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#0f172a classDef gatewayLayer fill:#f0fdf4,stroke:#10b981,stroke-width:2px,color:#064e3b classDef untrustedLayer fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#7f1d1d classDef functionNode fill:#e2e8f0,stroke:#64748b,stroke-width:2px,color:#0f172a %% ─── Subgraphs ─── subgraph Main ["MAIN PROCESS [Fully Trusted]"] M_DESC["Unrestricted Node.js Privileges"] IPC_MAIN_H[ipcMain.handle] IPC_MAIN_O[ipcMain.on] end subgraph Preload ["PRELOAD SCRIPT [Secure Gateway]"] P_DESC["Hermetically Sealed Context (Interface Narrowing)"] CTX_BRIDGE{contextBridge.exposeInMainWorld} end subgraph Renderer ["RENDERER [Zero-Trust Environment]"] R_DESC["Standard Browser Sandbox (No Node.js APIs)"] E_API([window.electronAPI]) end %% ─── Flow & Relationships ─── E_API ===>|Only Authorized Vector| CTX_BRIDGE CTX_BRIDGE --->|Transforms to narrow IPC calls| IPC_MAIN_H CTX_BRIDGE --->|Transforms to narrow IPC calls| IPC_MAIN_O %% ─── Apply Styles ─── class Main trustedLayer; class Preload gatewayLayer; class Renderer untrustedLayer; class M_DESC,P_DESC,R_DESC,IPC_MAIN_H,IPC_MAIN_O functionNode; class CTX_BRIDGE gatewayLayer; class E_API untrustedLayer; %% ─── Subgraph Backgrounds (Transparent for Native GitHub Support) ─── style Main fill:transparent,stroke:#3b82f6,stroke-width:2px,stroke-dasharray: 5 5 style Preload fill:transparent,stroke:#10b981,stroke-width:2px,stroke-dasharray: 5 5 style Renderer fill:transparent,stroke:#ef4444,stroke-width:2px,stroke-dasharray: 5 5 ``` The Preload Script functions as a **Secure Gateway** that performs **Interface Narrowing** — it transforms the broad, unrestricted IPC capabilities of the Main Process into a deliberately narrow, type-safe API surface. The renderer communicates with native functionality **exclusively** through this gateway. There are no alternative paths, no escape hatches, and no backdoors. ### Enforcement Configuration These settings are declared in `BrowserWindow.webPreferences` and are **non-negotiable**: | Setting | Value | Enforcement | | ------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `contextIsolation` | `true` | The Preload executes in a hermetically sealed V8 context. The renderer **cannot** access `require()`, Node.js globals, or any variable from the preload's scope. | | `nodeIntegration` | `false` | **Zero** Node.js API surface in the renderer. `fs`, `child_process`, `os`, `net`, and all built-in modules are completely unavailable. | | `sandbox` | `true` | The renderer process runs inside a **Chromium OS-level sandbox**, restricting system calls and file access at the kernel level. | | `webSecurity` | `true` | The same-origin policy is **strictly enforced**, preventing cross-origin data exfiltration from the renderer. | --- ## Standard Operating Procedure: The Three-Step Bridge Every native feature in this architecture **must** follow the Three-Step Bridge — a Standard Operating Procedure (SOP) that ensures traceability, type-safety, and auditability across the entire IPC surface. > [!IMPORTANT] > **Deterministic Synchronization**: Maintaining parity between the Main Process handler, the Preload Gateway exposure, and the TypeScript interface declaration is **mandatory**. A mismatch between any two of the three layers will result in either a **Type-Safety Gap** (silent failures in development) or a **Runtime Regression** (crashes in production). ### Step 1: Register the Handler — Main Process **File:** `apps/desktop/src/main/index.ts` ```typescript // COMMAND PATTERN: Use ipcMain.handle for request/response operations // The handler returns a value to the renderer via a resolved Promise. ipcMain.handle('feature:action', async (_event, arg1: string, arg2: number) => { // Validate inputs. Never trust data from the renderer. if (typeof arg1 !== 'string' || typeof arg2 !== 'number') { throw new Error('Invalid arguments'); } const result = await someNativeAPI(arg1, arg2); return result; }); // EVENT PATTERN: Use ipcMain.on for fire-and-forget operations // No return value — the renderer does not wait for a response. ipcMain.on('feature:fire', (_event, data: SomeType) => { performSideEffect(data); }); ``` **Channel naming convention:** `namespace:action` — examples: `printer:get-list`, `updater:check`, `app:get-version`. Namespaces must be unique, descriptive, and never generic. ### Step 2: Expose via contextBridge — Preload Gateway **File:** `apps/desktop/src/preload/index.ts` ```typescript const electronAPI = { // Command pattern exposure featureAction: (arg1: string, arg2: number): Promise => { return ipcRenderer.invoke('feature:action', arg1, arg2); }, // Event pattern exposure featureFire: (data: SomeType): void => { ipcRenderer.send('feature:fire', data); }, // Main→Renderer push events (with automatic lifecycle cleanup) onFeatureEvent: createEventSubscription('feature:event'), }; contextBridge.exposeInMainWorld('electronAPI', electronAPI); ``` **Non-negotiable rules:** - **Never** expose `ipcRenderer` directly — this is a **Catastrophic Failure** pattern. - **Never** expose `ipcRenderer.on` without cleanup — use `createEventSubscription()`, which returns an unsubscribe function for React `useEffect` lifecycle management. - **Always** declare explicit TypeScript types for all function signatures. ### Step 3: Declare the Interface — React Application **File:** `apps/web/src/types/electron.d.ts` ```typescript interface ElectronAPI { // ... existing methods ... featureAction: (arg1: string, arg2: number) => Promise; featureFire: (data: SomeType) => void; onFeatureEvent: (callback: (data: EventDataType) => void) => () => void; } ``` --- ## Verified Channel Manifest The following is the **complete, authoritative registry** of all authorized IPC channels. These channels are the **only** permitted vectors for native interaction. Any IPC channel not listed here is unauthorized and must be treated as a security anomaly. ### Printer Subsystem | Channel | Direction | Pattern | Payload | Access Control | | ------------------ | -------------------------- | ------------------- | --------------------------------------------------------------------- | ------------------------------ | | `printer:get-list` | Renderer → Main → Renderer | `invoke` / `handle` | Returns `ElectronPrinterInfo[]` | Read-only hardware enumeration | | `printer:print` | Renderer → Main → Renderer | `invoke` / `handle` | Accepts `ElectronPrintOptions`, returns `{ success, failureReason? }` | Controlled hardware invocation | **Main process handler:** `setupPrinterIPC()` in `src/main/index.ts` **Preload Gateway surface:** ```typescript getPrinters: () => ipcRenderer.invoke('printer:get-list'); print: (options?) => ipcRenderer.invoke('printer:print', options); ``` **React consumption hook:** `useElectronPrinter()` in `apps/web/src/hooks/use-electron-printer.ts` --- ### Auto-Updater Subsystem | Channel | Direction | Pattern | Payload | Access Control | | ----------------------- | --------------- | ------------------- | -------------------------------------------------------------- | ---------------------------------- | | `updater:check` | Renderer → Main | `invoke` / `handle` | Returns update check result | Read-only version query | | `updater:install` | Renderer → Main | `send` / `on` | No payload | Privileged: quits app and installs | | `updater:checking` | Main → Renderer | `send` | No payload | Status notification | | `updater:available` | Main → Renderer | `send` | `UpdateInfo { version, releaseDate, releaseNotes }` | Status notification | | `updater:not-available` | Main → Renderer | `send` | `UpdateInfo` | Status notification | | `updater:progress` | Main → Renderer | `send` | `ProgressInfo { percent, bytesPerSecond, transferred, total }` | Progress telemetry | | `updater:downloaded` | Main → Renderer | `send` | `UpdateInfo` | Status notification | | `updater:error` | Main → Renderer | `send` | Error message string | Error telemetry | **Main process handlers:** `setupAutoUpdaterIPC()` + `setupAutoUpdaterEvents()` in `src/main/index.ts` **React consumption hook:** `useElectronUpdater()` in `apps/web/src/hooks/use-electron-updater.ts` > [!NOTE] > The `updater:install` channel is the **highest-privilege IPC operation** in the system — it terminates the running process and launches a new binary. It should only be triggered by an explicit user action, never automatically. --- ## Extending the Bridge: Guided Walkthrough **Scenario:** Expose the application version to the React UI. ### 1. Main Process — Register Handler ```typescript // In app.whenReady() callback, src/main/index.ts ipcMain.handle('app:get-version', () => { return app.getVersion(); }); ``` ### 2. Preload Gateway — Expose Method ```typescript // Add to the electronAPI object, src/preload/index.ts const electronAPI = { // ... existing methods ... getAppVersion: (): Promise => { return ipcRenderer.invoke('app:get-version'); }, }; ``` ### 3. TypeScript Interface — Declare Type ```typescript // Add to ElectronAPI interface, apps/web/src/types/electron.d.ts interface ElectronAPI { // ... existing methods ... getAppVersion: () => Promise; } ``` ### 4. React — Consume ```tsx function VersionBadge() { const [version, setVersion] = useState(''); useEffect(() => { if (window.electronAPI) { window.electronAPI.getAppVersion().then(setVersion); } }, []); if (!version) return null; return v{version}; } ``` ### 5. Update This Manifest After implementing a new channel, **add it to the Verified Channel Manifest** in this document. Undocumented channels are unauthorized channels. --- ## Critical Audit Checklist: Anti-Patterns The following patterns constitute **critical security violations**. Each one expands the attack surface from "browser-level sandboxed web content" to "unrestricted OS-level code execution." Their presence in production code warrants **immediate incident response**. --- ### ❌ Exposing raw `ipcRenderer` ```typescript // VIOLATION: Catastrophic Failure — Total Attack Surface Expansion contextBridge.exposeInMainWorld('ipc', ipcRenderer); ``` **Threat:** The renderer gains **unrestricted IPC access** — it can invoke any channel, including channels that were never intended to be callable from the renderer. A single XSS vulnerability escalates to arbitrary native code execution. **Classification:** **Total System Compromise** --- ### ❌ Exposing `require` or Node.js APIs ```typescript // VIOLATION: Unauthenticated Code Execution contextBridge.exposeInMainWorld('require', require); ``` **Threat:** The renderer can `require('child_process').exec('rm -rf /')`. A single XSS vulnerability in _any_ dependency — including transitive ones — escalates to **full filesystem access, credential theft, reverse shells, and data exfiltration**. **Classification:** **Total System Compromise** --- ### ❌ Enabling `nodeIntegration` ```typescript // VIOLATION: Catastrophic Failure — Complete Boundary Collapse new BrowserWindow({ webPreferences: { nodeIntegration: true, contextIsolation: false }, }); ``` **Threat:** Every `