Desktop AI must treat the renderer as untrusted

Platform engineering SeedlingPlanted Sep 2026

I treat every desktop AI renderer as untrusted, even when it displays only our own interface. The renderer is where web content, user input, model output, and application state meet; any one of them can become executable influence through an XSS flaw or unsafe DOM path. Electron also sits beside Node.js and operating-system APIs, so collapsing that boundary can turn a rendering bug into host compromise. My default is therefore architectural distrust—the renderer may request narrowly defined work, but it never owns the authority to perform it.

This starts with the process hierarchy. The main process owns Node.js and OS capabilities; the preload script is a small bridge in an isolated JavaScript world; the renderer gets neither. nodeIntegration stays off, contextIsolation stays on, and the renderer sandbox stays enabled. Those settings work together to prevent page code from sharing the preload heap, reaching Node directly, or inheriting unnecessary host capability. The point, as with sizing a sandbox to the blast radius, is to make renderer compromise survivable rather than improbable.

The preload bridge should expose operations, not plumbing. Passing through raw ipcRenderer, generic callbacks, or an exec-shaped escape hatch simply relocates privilege without constraining it. I prefer one handle per operation—read a named configuration value, store a credential, choose a file—with explicit arguments and return types. This is the desktop equivalent of treating tool schemas as security boundaries: the API defines the complete language in which an untrusted caller can ask for effects.

Every main-process handler then applies three checks in sequence. First, validate the sender using a parsed event.senderFrame.url against an allowlist; string-prefix checks are too easy to bypass. Second, validate the payload at runtime against a closed schema. Third, execute only the scoped capability named by that channel. TypeScript types and channel registries make this surface auditable, but cannot validate runtime input. Until sender and schema both pass, the data is untrusted—and model output remains renderer-untrusted regardless of how confident or well-formed it looks.

That last rule matters because a desktop AI application joins two escalation paths. Hostile content can steer the model, and model output can steer the interface. If the renderer holds credentials or a broad IPC bridge, an injected instruction can cross from text to machine authority. This is why prompt injection is a permissions problem here too. LLM API traffic should originate in the main process, preferably through an isolated persistent session, while API credentials remain in OS-backed storage and never enter renderer state or page-accessible storage.

Renderer distrust also governs what leaves the window. Navigation is denied unless the destination origin is allowed. New windows are denied by default. shell.openExternal() receives a strict protocol allowlist, because handing it an arbitrary URL delegates to platform handlers that may do far more than open a browser. Embedded web content gets separate session and permission controls, with preloads removed and sources checked before attachment. A restrictive CSP blocks inline and evaluated script as defence in depth; it does not excuse a privileged renderer.

There is one precise concession: a renderer that loads only packaged, path-safe local assets has less exposure than one that embeds remote pages, so its allowlists and session layout can be simpler. It still should not receive Node integration, raw IPC, or secrets, because local content can process untrusted files and model-generated text, while bundled Chromium defects arrive on the application vendor’s patch cadence. Reduced exposure justifies fewer routes across the boundary—not a weaker boundary.

I want the resulting design to be boring to audit. A reviewer should be able to enumerate every renderer-to-main capability, its permitted sender, its input schema, and its side effect; verify that credentials and networking stay outside the page; and see sandboxing, CSP, navigation controls, fuses, code signing, and update integrity as reinforcing layers. That resembles runtime contracts for probabilistic agents: uncertain output is acceptable only when deterministic boundaries govern what it can cause. The renderer can be compromised without becoming the computer.