Security
Electron’s security model requires that sensitive BrowserWindow options — particularly webPreferences — are never controllable from renderer code. This library enforces that boundary.
Enforced security floor
Section titled “Enforced security floor”The library enforces a non-negotiable baseline on every child window, regardless of consumer config:
nodeIntegration: falsecontextIsolation: truesandbox: true(default — can be explicitly set tofalseviadefaultWindowOptions.webPreferences.sandbox)
Beyond this floor, webPreferences is only configurable via setupWindowManager in the main process. Renderer-supplied props cannot reach webPreferences — they’re filtered through an allowlist before touching BrowserWindow.
What the renderer cannot control
Section titled “What the renderer cannot control”webPreferences— main-process-only. Renderer props can’t overridenodeIntegration,contextIsolation,preload, etc.- Arbitrary URLs — child windows load only
about:blank. Other URLs rejected bysetWindowOpenHandler.
Origin allowlist
Section titled “Origin allowlist”Two mechanisms restrict which renderer origins can use the IPC surface:
Runtime — works whether or not you bundle your main process:
setupWindowManager({ allowedOrigins: ["app://main", "file://"],});Validated per IPC call against the parent renderer’s top-frame origin. Unset → permissive (dev warning). ["*"] → explicitly allow all.
Build-time — if you bundle both main and preload, define a constant in your bundler:
// vite / esbuild / webpack DefinePlugin — for BOTH main and preload buildsdefine: { __ELECTRON_WINDOW_ALLOWED_ORIGINS__: JSON.stringify(["app://main", "file://"]),}This additionally gates the preload: on a wrong origin, window.electron_window is never exposed. The check runs in the generated IPC validator against event.senderFrame.url (the correct frame).
Per-WebContents ownership
Section titled “Per-WebContents ownership”A renderer can only mutate windows it registered. If you setupForWindow on multiple parent windows, each is isolated — WebContents B cannot DestroyWindow, UpdateWindow, or WindowAction on WebContents A’s child windows, even with knowledge of the window ID.
setWindowOpenHandler also verifies ownership: a different WebContents calling window.open("about:blank", stolenId) is denied.
Grandchild windows
Section titled “Grandchild windows”Child windows created by <Window> have a blanket setWindowOpenHandler(() => ({ action: "deny" })). Any window.open() from inside a child window’s document returns null.
This doesn’t affect nested <Window> components — they call window.open() from the parent renderer’s context (React runs in the parent; only the DOM is portaled into the child). But third-party libraries that call element.ownerDocument.defaultView.open(...) from inside a child window’s DOM will be blocked.
A dev warning fires when this happens, naming the denied URL.
Rate limits & TTL
Section titled “Rate limits & TTL”Window creation is rate-limited and pending registrations have a TTL to prevent orphaned-registration DoS:
| Option | Default | Description |
|---|---|---|
maxPendingWindows | 100 | Max windows awaiting creation |
maxWindows | 50 | Max total open windows |
| Pending TTL | 10s | Stale pending entries auto-evicted |
The 10-second TTL is typically invisible (the RegisterWindow → window.open handshake takes microseconds). If debugging with a breakpoint between register and open, the entry may expire — reopen the window.
Window IDs
Section titled “Window IDs”IDs are generated via crypto.randomUUID() (falling back to crypto.getRandomValues in non-secure contexts). 122 bits of entropy makes guessing infeasible — cross-WebContents attacks require observation (DevTools, leaked state), not brute force.
Summary
Section titled “Summary”| Threat | Mitigation |
|---|---|
Renderer overrides webPreferences | Main-process-only + enforced floor (nodeIntegration: false, etc.) |
| Arbitrary URL navigation | about:blank only; others denied |
| Unexpected IPC props | Allowlist filtering before BrowserWindow |
| IPC from iframes | Main-frame-only validator in the IPC layer — rejected before handlers run |
| Wrong-origin renderer | allowedOrigins (runtime, per-call) or __ELECTRON_WINDOW_ALLOWED_ORIGINS__ |
| Cross-renderer window control | Per-WebContents ownership checks on every mutation |
| Runaway window creation | maxPendingWindows, maxWindows, 10s pending TTL |
| Guessable window IDs | crypto.randomUUID() — 122 bits of entropy |