diff --git a/frontend/electron/main.ts b/frontend/electron/main.ts index 7cb0b3b..cc579da 100644 --- a/frontend/electron/main.ts +++ b/frontend/electron/main.ts @@ -113,6 +113,46 @@ async function backendRequest(method: string, params: Record): return response; } +/** + * Methods the renderer is allowed to invoke, enforced in the main process so a + * compromised page cannot invent new backend calls. Everything else is + * rejected. Sensitive methods are listed because their screens need them; they + * remain gated by the vault password on the backend side. + */ +const RENDERER_METHODS: ReadonlySet = new Set([ + // Handled natively by Electron main (dialogs, HTTP). + 'pick_image', + 'link_preview', + 'upload_image', + // Forwarded to the Rust backend over stdio. + 'init', + 'get_state', + 'create_profile', + 'select_profile', + 'publish_note', + 'feed_get', + 'relay_add', + 'relay_remove', + 'relay_set_enabled', + 'relay_test', + 'settings_update', + 'backup_now', + 'set_vault_password', + 'unlock_vault', + 'lock_vault', + 'remove_vault_password', + 'reveal_secret_key', + 'signer_connect', + 'signer_disconnect', + 'signer_status', + 'signer_approve', +]); + +/** True when `method` may be dispatched. Unknown methods never reach the backend. */ +function isAllowedMethod(method: unknown): method is string { + return typeof method === 'string' && RENDERER_METHODS.has(method); +} + const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'avif']; /** How long to wait for a link-preview page before giving up. */ @@ -346,6 +386,15 @@ app.whenReady().then(() => { ipcMain.handle( 'backend:request', async (_event, payload: { method: string; params?: Record }) => { + if (!isAllowedMethod(payload?.method)) { + console.warn('[backend] rejected renderer method:', String(payload?.method)); + return { + status: 'error', + code: 'unknown_method', + message: 'That operation is not permitted.', + details: null, + }; + } const params = payload.params ?? {}; // Media and network tasks are handled here (Electron) rather than the // Rust backend: they need a native file dialog, the hosting upload, and