/** * desktop/src/window.ts — creates the single BrowserWindow that hosts the * unchanged web frontend, loaded from either the embedded localhost server or a * selected remote tunnel host (D-1, PLAN_NATIVE_TUNNEL / C-Desktop). * * Hardening (DESKTOP_PLAN §8 / TECH_DOC §7): contextIsolation on, nodeIntegration * off, sandbox on, preload restricted to a minimal contextBridge. We deny every * new-window request and block navigation to any foreign origin — a defence-in- * depth guard against a hijacked page trying to escape the active origin. * * D-1 note: the origin lock is DYNAMIC. The window may be pointed (by main.ts, * via a programmatic loadURL — which does NOT fire will-navigate) at exactly one * host at a time: the embedded `http://127.0.0.1:` origin OR the selected * remote `https://.terminal.yaojia.wang` origin. `getAllowedOrigin` returns * whichever is active NOW, so renderer-initiated navigation stays locked to that * single origin and everything else is still blocked. */ import { BrowserWindow } from 'electron' const WINDOW_WIDTH = 1100 const WINDOW_HEIGHT = 720 const BACKGROUND_COLOR = '#0e0f13' /** Parse the origin of a URL, returning null for anything malformed. */ export function originOf(url: string): string | null { try { return new URL(url).origin } catch { return null } } /** * Create the main window. `url` is the initial page to load; `getAllowedOrigin` * is queried live on every navigation attempt and must return the origin the * window is currently allowed on (or null to allow nothing / fail closed). */ export function createMainWindow( url: string, preloadPath: string, getAllowedOrigin: () => string | null, ): BrowserWindow { const win = new BrowserWindow({ width: WINDOW_WIDTH, height: WINDOW_HEIGHT, backgroundColor: BACKGROUND_COLOR, webPreferences: { contextIsolation: true, nodeIntegration: false, sandbox: true, preload: preloadPath, }, }) // Never spawn child windows; the frontend has no legitimate reason to. win.webContents.setWindowOpenHandler(() => ({ action: 'deny' })) // Block navigation away from the currently-active (local or remote) origin. win.webContents.on('will-navigate', (event, targetUrl) => { const allowedOrigin = getAllowedOrigin() // Fail closed: if we can't determine the active origin, allow nothing. if (allowedOrigin === null || originOf(targetUrl) !== allowedOrigin) { event.preventDefault() } }) void win.loadURL(url) return win }