Treat every renderer message as untrusted input. TypeScript makes application code easier to maintain, but its types are erased at runtime and do not stop a compromised renderer from calling a known physical channel with arbitrary values.
This guide supplements Electron's security guidance; it does not replace it.
Use a context-isolated, sandboxed renderer:
new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
preload: preloadPath,
},
});
Expose the generated bridge with contextBridge. It contains wrappers only for
statically declared channels and does not expose raw ipcRenderer.send,
invoke, or on. Never add a generic method that accepts a channel string;
doing so bypasses that allowlist.
Bundle a sandboxed preload to one self-contained CommonJS file and keep
electron external. See
build and preload troubleshooting.
Use authorize for rules shared by a module:
// Channel keys this module accepts only from the trusted application frame.
const privilegedChannels = new Set(["read", "write"]);
const registerFilesIpc = defineIpcModule("files", channels, {
authorize: (event, context) => {
if (!privilegedChannels.has(context.key)) return true;
const url = event.senderFrame?.url;
if (!url) return false;
const source = new URL(url);
return source.protocol === "app:" && source.hostname === "local";
},
});
Make the allow decision from trusted main-process state. A renderer-provided role, user ID, file root, or capability flag is input, not proof.
Important details:
senderFrame can be null; fail closed when the rule requires a frame.startsWith("https://example.com") checks can accept lookalike hosts.authorize runs before validation and the channel callback. Returning exactly
false produces IpcAuthorizationError; a thrown error is preserved.Authorization is often module-wide, but permission may depend on
context.key. Split modules when their trust levels or ownership differ
substantially.
The validate map is keyed by the module's channel keys. A callback validator
may inspect the raw arguments, event, and channel context:
defineIpcModule("files", channels, {
validate: {
read: (args) => {
if (args.length !== 1 || typeof args[0] !== "string") {
throw new TypeError("expected one path");
}
},
},
});
For parsing, use a Standard Schema implementation such as Zod, Valibot, or ArkType. The schema validates the full argument tuple, not only the first argument:
import { z } from "zod";
const readArgs = z.tuple([z.string().min(1).max(4096)]);
defineIpcModule(
"files",
{
read: handle((_event, path: string) => readAllowedFile(path)),
},
{
validate: { read: readArgs },
},
);
Successful parsed output replaces the arguments passed to the callback. This
means coercion and stripped fields take effect, and the schema output must
match the handler's parameter tuple. Schema failures become
IpcValidationError and preserve their issues.
Validation should constrain meaning as well as shape:
createIpcEmitter().emit() sends to every live BrowserWindow, including
hidden windows. Do not broadcast user-specific, tenant-specific, or otherwise
sensitive data. Prefer emitTo(webContents, ...) when one renderer owns the
result.
Event namespacing prevents accidental channel overlap, not unauthorized observation. The generated bridge narrows what application code can subscribe to, while main-process routing still decides which window receives the data.
An invoke rejection travels back to ipcRenderer.invoke, but Electron does not
preserve every custom error property across that boundary. Log a diagnostic in
the main process and return a stable, non-sensitive application error to the
renderer where appropriate.
Fire-and-forget listeners have no response channel. Configure
onListenerError so failures reach application logging rather than becoming
unhandled rejections. Avoid logging raw secrets, tokens, full file contents, or
unbounded attacker-controlled values.
The exact propagation rules are documented in the error contract.
contextIsolation: true, nodeIntegration: false, and a
sandbox unless a documented constraint requires otherwise.WebContents.