diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 37e42052..208082ed 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -327,7 +327,7 @@ npm install -g omniroute mkdir -p ~/.omniroute # Create .env file (see .env.example) -cp ~/.omniroute/.env +cp .env.example ~/.omniroute/.env # Start server omniroute @@ -421,7 +421,7 @@ For host-integrated mode with CLI binaries, see the Docker section in the main d | `AUTH_COOKIE_SECURE` | `false` | Force `Secure` auth cookie (behind HTTPS reverse proxy) | | `OMNIROUTE_MEMORY_MB` | `256` | Node.js heap limit in MB | | `PROMPT_CACHE_MAX_SIZE` | `50` | Max prompt cache entries | -| `SEMANTIC_CACHE_MAX_SIZE`| `100` | Max semantic cache entries | +| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max semantic cache entries | For the full environment variable reference, see the [README](../README.md). diff --git a/electron/main.js b/electron/main.js index 7edd51c0..cfffc8b2 100644 --- a/electron/main.js +++ b/electron/main.js @@ -251,14 +251,16 @@ function setupIpcHandlers() { // Get data directory ipcMain.handle('get-data-dir', () => { - const home = require('os').homedir(); - return path.join(home, '.omniroute'); + return app.getPath('userData'); }); // Restart server ipcMain.handle('restart-server', async () => { + const serverToStop = nextServer; stopNextServer(); - await new Promise(resolve => setTimeout(resolve, 1000)); + if (serverToStop) { + await new Promise(resolve => serverToStop.once('exit', resolve)); + } startNextServer(); return { success: true }; }); diff --git a/electron/preload.js b/electron/preload.js index e7c299a3..a6babd46 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -29,6 +29,16 @@ const VALID_CHANNELS = { ], }; +/** + * Validate IPC channel name for security + * @param {string} channel - The channel to validate + * @param {'invoke' | 'send' | 'receive'} type - The channel type + * @returns {boolean} + */ +function isValidChannel(channel, type) { + return VALID_CHANNELS[type]?.includes(channel) ?? false; +} + /** * Expose a limited API to the renderer process */ @@ -37,47 +47,81 @@ contextBridge.exposeInMainWorld('electronAPI', { * Get application information * @returns {Promise<{name: string, version: string, platform: string, isDev: boolean, port: number}>} */ - getAppInfo: () => ipcRenderer.invoke('get-app-info'), + getAppInfo: () => { + if (!isValidChannel('get-app-info', 'invoke')) { + return Promise.reject(new Error('Invalid channel')); + } + return ipcRenderer.invoke('get-app-info'); + }, /** * Open an external URL in the default browser * @param {string} url - The URL to open */ - openExternal: (url) => ipcRenderer.invoke('open-external', url), + openExternal: (url) => { + if (!isValidChannel('open-external', 'invoke')) { + return Promise.reject(new Error('Invalid channel')); + } + return ipcRenderer.invoke('open-external', url); + }, /** * Get the data directory path * @returns {Promise} */ - getDataDir: () => ipcRenderer.invoke('get-data-dir'), + getDataDir: () => { + if (!isValidChannel('get-data-dir', 'invoke')) { + return Promise.reject(new Error('Invalid channel')); + } + return ipcRenderer.invoke('get-data-dir'); + }, /** * Restart the server * @returns {Promise<{success: boolean}>} */ - restartServer: () => ipcRenderer.invoke('restart-server'), + restartServer: () => { + if (!isValidChannel('restart-server', 'invoke')) { + return Promise.reject(new Error('Invalid channel')); + } + return ipcRenderer.invoke('restart-server'); + }, /** * Minimize the window */ - minimizeWindow: () => ipcRenderer.send('window-minimize'), + minimizeWindow: () => { + if (isValidChannel('window-minimize', 'send')) { + ipcRenderer.send('window-minimize'); + } + }, /** * Maximize/unmaximize the window */ - maximizeWindow: () => ipcRenderer.send('window-maximize'), + maximizeWindow: () => { + if (isValidChannel('window-maximize', 'send')) { + ipcRenderer.send('window-maximize'); + } + }, /** * Close the window */ - closeWindow: () => ipcRenderer.send('window-close'), + closeWindow: () => { + if (isValidChannel('window-close', 'send')) { + ipcRenderer.send('window-close'); + } + }, /** * Listen for server status updates * @param {function} callback - Callback function */ onServerStatus: (callback) => { - ipcRenderer.on('server-status', (event, data) => callback(data)); + if (isValidChannel('server-status', 'receive')) { + ipcRenderer.on('server-status', (event, data) => callback(data)); + } }, /** @@ -92,7 +136,9 @@ contextBridge.exposeInMainWorld('electronAPI', { * @param {function} callback - Callback function */ onPortChanged: (callback) => { - ipcRenderer.on('port-changed', (event, port) => callback(port)); + if (isValidChannel('port-changed', 'receive')) { + ipcRenderer.on('port-changed', (event, port) => callback(port)); + } }, /**