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:

  1. Preload-Kontext — dein vertrauenswürdiger Code mit Zugriff auf Electron-APIs
  2. Web-Kontext — der HTML/JS-Code, der von index.html geladen 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:

JavaScript preload.js — UNSICHER ohne contextIsolation
const { ipcRenderer } = require('electron');

window.deleteFile = (path) => ipcRenderer.invoke('files:delete', path);

Mit contextIsolation: false:

  • Eine kompromittierte Webseite könnte window.ipcRenderer direkt nutzen
  • Sie könnte window.deleteFile = console.log setzen und so deine Funktion ersetzen
  • Sie könnte Prototype-Pollution betreiben: Array.prototype.toString = ...

Mit contextIsolation: true:

  • window.ipcRenderer existiert im Web-Kontext nicht
  • window.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-VersionDefault für contextIsolation
1 – 5false
6 – 11false, 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:

JavaScript preload.js — RICHTIG
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-Code window.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:

  1. contextIsolation: true in webPreferences setzen
  2. Preload umschreiben: alle window.x = ... zu contextBridge.exposeInMainWorld('x', ...) umstellen
  3. Renderer-Code prüfen: Aufrufe von require('electron') oder direktes ipcRenderer muss raus — alles geht jetzt nur über die Bridge
  4. Funktionen einzeln exposen: nicht das ganze ipcRenderer durchschleifen, sondern dedizierte Wrapper
JavaScript Alt — UNSICHER
// preload.js mit contextIsolation: false
window.api = require('./my-api');

// renderer.js
window.api.deleteFile('/foo');
JavaScript Modern — sicher
// 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.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Renderer & Preload

Zur Übersicht