- renderer
No Node, no Electron, no native imports.
The renderer never imports node:*, electron, better-sqlite3, node-pty, child_process, or fs. ESLint enforces it.
- preload
Pass-through only.
Preload exposes typed contextBridge surfaces; no business logic, no state, no side effects beyond invoke.
- main
Validate at the IPC boundary.
ipcMain handlers validate renderer input. Filesystem paths resolve against the project root; symlinks canonicalize through realpath.
- agent exec
Allowlist + hard-deny regex.
Read-oriented binaries (git status|diff|log|…, npm run typecheck|test|lint|build:renderer, tsc, eslint, ruff, rg, grep, find, ls, cat). Deny: shell metacharacters (; | & $( \ >), sudo, chmod 777, chown, destructive patterns (rm -rf, git reset --hard, git push --force, drop table|database, supabase db reset).
- secrets
OS keychain at rest.
Electron safeStorage. Keychain on macOS, DPAPI on Windows, libsecret on Linux. Plaintext fallback explicitly marked in Settings when keychain is unavailable. Keys never reach the renderer.
- csp
Strict in prod, relaxed in dev.
Production CSP locks script-src; dev relaxes for Vite HMR and Monaco blob workers. connect-src allowlists localhost, 127.0.0.1, api.anthropic.com, api.openai.com.
- permissions
Default-deny on the main session.
setPermissionRequestHandler refuses all permission requests by default. The Preview partition opens browser-grade permissions only for that webview.
- external nav
No in-app navigation.
setWindowOpenHandler denies in-app navigation and routes through shell.openExternal. app:openExternal accepts only http(s):// URLs.
- shutdown
Group-kill spawned children.
Detached spawn + negative-PID signal on SIGINT/SIGTERM/SIGHUP and app quit. Vite + esbuild + Next children clean up together.