ADR 0026 — Electron CSP + permission hardening
Accepted · 2026-07-02
Context
Section titled “Context”Two renderer-facing hardening gaps survived the earlier security passes (ADR 0004, ADR 0006):
- Web permissions. Electron grants many web-platform permission requests by default (notifications, media, fullscreen, clipboard, …). Restura’s renderer needs almost none of them — every privileged operation (native notifications, filesystem, network) already goes through the validated IPC surface — but nothing asserted that, so a compromised renderer could still exercise Electron’s default-grant behavior.
- CSP directives. The production CSP (header policy in
electron/main/main.ts, mirrored as a<meta>fallback invite.config.mtsfor thefile://load path) had noobject-srcorworker-srcdirective, and nothing checked that the two hand-maintained copies stayed identical.
Decision
Section titled “Decision”- Default-deny permission handlers on
session.defaultSession: bothsetPermissionRequestHandlerandsetPermissionCheckHandlerreject every permission exceptclipboard-sanitized-write(needed by the renderer’s copy buttons vianavigator.clipboard.writeText). The allowlist is a singleALLOWED_WEB_PERMISSIONSset inelectron/main/main.ts; applied in dev and prod alike. Native notifications are unaffected — they go through the main-processNotificationAPI over IPC, not the web permission model. - CSP additions:
object-src 'none'andworker-src 'self' file:in both copies. Monaco’s editor workers are Vite?workerchunks loaded same-origin, so noblob:source is needed.connect-src 'self' https: wss:stays deliberately broad — the renderer talks directly to https origins on the web transport path and for Sentry. - Parity is enforced, not assumed:
electron/main/__tests__/security-hardening.test.tsparses both policies out of the sources and fails if they diverge (modulo the header-onlyframe-ancestors), ifscript-srcever grows'unsafe-eval', or if the permission allowlist grows without the test being updated deliberately.
Consequences
Section titled “Consequences”Positive
- The renderer’s web-permission surface is now closed by default; Electron’s default-grant behavior can’t silently re-appear.
- CSP drift between
main.tsandvite.config.mtsis caught by a unit test instead of relying on code review.
Negative / process
- Any future feature that needs a web permission (e.g. camera/microphone, web notifications) must add it to
ALLOWED_WEB_PERMISSIONSand update the structural test — an explicit, reviewable security decision. - The CSP is still maintained in two places (a runtime header and a build-time meta tag); the test reduces but does not remove that duplication.
Related
Section titled “Related”- ADR 0004 — Security Hardening — the earlier hardening pass this one closes gaps in.
- ADR 0006 — Electron Connection Cleanup + Pre-flight DNS Guard — the sibling renderer-facing hardening pass.