contextIsolation ist die Sicherheitsgrenze zwischen deinem Preload-Code und dem Web-Inhalt im selben Renderer. Ohne sie könnte eine kompromittierte Webseite (XSS, bösartige Werbung) deinen Preload-Code überschreiben und das volle Node-API erlangen — Spielfeld offen. Default ist true seit Electron 12; und das sollte sie auch immer sein.
Was contextIsolation eigentlich macht
Im Renderer-Prozess laufen zwei JavaScript-Kontexte parallel:
- Preload-Kontext — dein vertrauenswürdiger Code mit Zugriff auf Electron-APIs
- Web-Kontext — der HTML/JS-Code, der von
index.htmlgeladen wird
Mit contextIsolation: true haben diese Kontexte getrennte JavaScript-Heaps. Der Web-Code sieht nicht, was im Preload passiert — keine Variablen, keine Funktionen, kein electron-Modul. Die einzige Brücke: contextBridge.
Mit contextIsolation: false teilen sich beide den selben Heap. window.x = foo im Preload ist dann auch im Web-Code sichtbar — und umgekehrt kann der Web-Code Preload-Variablen überschreiben.
Warum das wichtig ist
Stell dir vor, dein Preload sieht so aus:
const { ipcRenderer } = require('electron');
window.deleteFile = (path) => ipcRenderer.invoke('files:delete', path);Mit contextIsolation: false:
- Eine kompromittierte Webseite könnte
window.ipcRendererdirekt nutzen - Sie könnte
window.deleteFile = console.logsetzen und so deine Funktion ersetzen - Sie könnte Prototype-Pollution betreiben:
Array.prototype.toString = ...
Mit contextIsolation: true:
window.ipcRendererexistiert im Web-Kontext nichtwindow.deleteFile = ...würde nur in einem isolierten Web-Heap geändert — Preload bleibt unangetastet- Prototype-Pollution wirkt nur im Web-Heap — Preload ist sicher
Default-Wert seit Electron 12
| Electron-Version | Default für contextIsolation |
|---|---|
| 1 – 5 | false |
| 6 – 11 | false, aber mit Deprecation-Warning |
| 12+ | true |
In jeder modernen Electron-Version ist contextIsolation aktiv, sofern du es nicht explizit ausschaltest. Wer ein altes Projekt migriert, sollte es prüfen.
Wie der Preload trotzdem etwas exponieren kann
window.foo = ... direkt funktioniert nicht — die Heaps sind getrennt. Lösung: contextBridge:
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('api', {
deleteFile: (path) => ipcRenderer.invoke('files:delete', path)
});exposeInMainWorld macht eine spezielle Cross-Heap-Brücke: das Objekt wird in den Web-Heap kopiert (mit serialisierter Funktions-Referenz). Der Web-Code sieht window.api, aber kann den Preload-Originalwert nicht erreichen.
Was die Trennung NICHT bringt
contextIsolation schützt vor:
- Prototype-Pollution aus dem Web-Code
- Direktem Zugriff auf Electron-APIs vom Web aus
- Versehentlicher Variable-Vermischung
Sie schützt NICHT vor:
- Schwachstellen in den Funktionen, die du selbst exposest. Wenn du
delete: (path) => fs.unlink(path)exposest und der Web-Codewindow.api.delete('/etc/passwd')ruft — pech. - XSS auf der Web-Seite an sich. Eine kompromittierte Webseite kann immer noch deine API-Funktionen aufrufen.
- Daten-Leaks über das
window.api-Objekt (wenn der Web-Code Zugriff hat, kann er die Funktionen aufrufen).
Daher: zusätzlich zur contextIsolation auch immer Input-Validierung im Main-Handler und CSP im HTML.
Migration von alter Konfig
Wer ein altes Projekt mit contextIsolation: false modernisiert:
contextIsolation: trueinwebPreferencessetzen- Preload umschreiben: alle
window.x = ...zucontextBridge.exposeInMainWorld('x', ...)umstellen - Renderer-Code prüfen: Aufrufe von
require('electron')oder direktesipcRenderermuss raus — alles geht jetzt nur über die Bridge - Funktionen einzeln exposen: nicht das ganze
ipcRendererdurchschleifen, sondern dedizierte Wrapper
// preload.js mit contextIsolation: false
window.api = require('./my-api');
// renderer.js
window.api.deleteFile('/foo');// preload.js mit contextIsolation: true
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('api', {
deleteFile: (path) => ipcRenderer.invoke('files:delete', path)
});
// main.js
ipcMain.handle('files:delete', (_event, path) => {
// VALIDIEREN!
if (!path.startsWith(app.getPath('userData'))) return false;
return fs.unlink(path);
});
// renderer.js bleibt gleich
await window.api.deleteFile('/foo');Häufige Stolperfallen
contextIsolation: false ist die rote Linie.
Es gibt heute praktisch keinen guten Grund, das auszuschalten. Wer's tut, braucht schriftlich das OK eines Sicherheits-Reviews — und auch dann ist es selten nötig.
window.foo = ... im Preload bleibt unsichtbar.
Klassischer Fehler bei Migration: man setzt contextIsolation: true, das alte window.foo = ... aus dem Preload verschwindet — und versteht nicht warum. Antwort: getrennte Heaps. Lösung: contextBridge.exposeInMainWorld(...).
Nicht das ganze ipcRenderer exposen.
contextBridge.exposeInMainWorld('ipc', ipcRenderer) ist faul und unsicher. Damit kann der Renderer auf jeden Channel pushen — auch interne. Stattdessen: pro Funktion einen Wrapper, der nur seinen Channel kennt.
Sandbox + contextIsolation = Belt and Suspenders.
Beide aktivieren. Sandbox schränkt ein, was der Renderer überhaupt kann; contextIsolation trennt zusätzlich Preload und Web. Beides zusammen ist die heutige Default-Sicherheitsbasis.
Validation im Main, nicht im Preload.
Das Preload ist eine dünne Bridge, kein Sicherheits-Layer. Alle Sicherheitschecks (erlaubte Pfade, erlaubte URLs etc.) gehören in den Main-Handler. Sonst kann ein kompromittierter Web-Code seine Anfragen direkt durchschleifen.
Klassen und nicht-serialisierbare Werte können NICHT exposed werden.
contextBridge kopiert das Objekt zwischen Heaps. Funktionen werden als „Proxy-Funktionen" exponiert (Aufruf geht durch). Aber Klassen-Instanzen, DOM-Knoten, Date-Objekte etc. werden nicht sauber rüber kopiert. Bei Bedarf serialisieren.