@@ -163,6 +163,13 @@ function hasOrigin(raw: string, expected: string): boolean {
163163 }
164164}
165165
166+ function assertTrustedSender ( event : Electron . IpcMainInvokeEvent ) : void {
167+ const frame = event . senderFrame
168+ if ( hostOrigin === undefined || frame === null || frame !== event . sender . mainFrame || ! hasOrigin ( frame . url , hostOrigin ) ) {
169+ throw new Error ( 'desktop update IPC rejected an untrusted sender' )
170+ }
171+ }
172+
166173/** Install navigation and permission policy before the first renderer loads. */
167174function hardenSession ( ) : void {
168175 const desktopSession = session . defaultSession
@@ -234,24 +241,40 @@ async function createMainWindow(): Promise<BrowserWindow> {
234241 return window
235242}
236243
237- ipcMain . handle ( 'pythinker:update:get' , ( ) => getUpdateState ( ) )
238- ipcMain . handle ( 'pythinker:update:set-auto' , ( _event , enabled : unknown ) => {
244+ function showWindowSafely ( ) : void {
245+ void lifecycle ?. showWindow ( ) . catch ( ( error : unknown ) => {
246+ console . error ( 'desktop window failed to open:' , error )
247+ } )
248+ }
249+
250+ ipcMain . handle ( 'pythinker:update:get' , ( event ) => {
251+ assertTrustedSender ( event )
252+ return getUpdateState ( )
253+ } )
254+ ipcMain . handle ( 'pythinker:update:set-auto' , ( event , enabled : unknown ) => {
255+ assertTrustedSender ( event )
239256 if ( typeof enabled !== 'boolean' ) throw new TypeError ( 'automatic updates must be a boolean' )
240257 return setAutoUpdate ( enabled )
241258} )
242- ipcMain . handle ( 'pythinker:update:check' , ( ) => checkForUpdatesNow ( ) )
243- ipcMain . handle ( 'pythinker:update:install' , ( ) => quitAndInstallNow ( ) )
259+ ipcMain . handle ( 'pythinker:update:check' , ( event ) => {
260+ assertTrustedSender ( event )
261+ return checkForUpdatesNow ( )
262+ } )
263+ ipcMain . handle ( 'pythinker:update:install' , ( event ) => {
264+ assertTrustedSender ( event )
265+ return quitAndInstallNow ( )
266+ } )
244267
245268function createTray ( images : TrayImages ) : void {
246269 tray = new Tray ( images . idle )
247270 tray . setToolTip ( APP_NAME )
248271 const template : MenuItemConstructorOptions [ ] = [
249- { label : 'Open Pythinker' , click : ( ) => { void lifecycle ?. showWindow ( ) } } ,
272+ { label : 'Open Pythinker' , click : showWindowSafely } ,
250273 { type : 'separator' } ,
251274 { label : 'Quit' , click : ( ) => { void requestAppQuit ( ) } } ,
252275 ]
253276 tray . setContextMenu ( Menu . buildFromTemplate ( template ) )
254- tray . on ( 'click' , ( ) => { void lifecycle ?. showWindow ( ) } )
277+ tray . on ( 'click' , showWindowSafely )
255278}
256279
257280function releaseAppQuit ( ) : void {
@@ -333,8 +356,8 @@ async function boot(): Promise<void> {
333356if ( ! app . requestSingleInstanceLock ( ) ) {
334357 app . quit ( )
335358} else {
336- app . on ( 'second-instance' , ( ) => { void lifecycle ?. showWindow ( ) } )
337- app . on ( 'activate' , ( ) => { void lifecycle ?. showWindow ( ) } )
359+ app . on ( 'second-instance' , showWindowSafely )
360+ app . on ( 'activate' , showWindowSafely )
338361 app . on ( 'window-all-closed' , ( ) => {
339362 // Tray and Host own application lifetime on every platform.
340363 } )
0 commit comments