fix: Address CodeRabbit security and robustness review comments

- Guard against NaN ports from non-numeric env variables in constants.ts
- Validate IPC sender before returning API key to prevent leaking to
  untrusted senders (webviews, additional windows)
- Filter dialog properties to maintain file-only intent and prevent
  renderer from requesting directories via OPEN_FILE
- Fix Windows VS Code URL paths by ensuring leading slash after 'file'

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Shirone
2026-01-25 21:02:53 +01:00
parent 0b4e9573ed
commit 2de3ae69d4
4 changed files with 34 additions and 17 deletions

View File

@@ -22,8 +22,11 @@ export const DEFAULT_HEIGHT = 950;
// When launched via root init.mjs we pass: // When launched via root init.mjs we pass:
// - PORT (backend) // - PORT (backend)
// - TEST_PORT (vite dev server / static) // - TEST_PORT (vite dev server / static)
export const DEFAULT_SERVER_PORT = parseInt(process.env.PORT || '3008', 10); // Guard against NaN from non-numeric environment variables
export const DEFAULT_STATIC_PORT = parseInt(process.env.TEST_PORT || '3007', 10); const parsedServerPort = Number.parseInt(process.env.PORT ?? '', 10);
const parsedStaticPort = Number.parseInt(process.env.TEST_PORT ?? '', 10);
export const DEFAULT_SERVER_PORT = Number.isFinite(parsedServerPort) ? parsedServerPort : 3008;
export const DEFAULT_STATIC_PORT = Number.isFinite(parsedStaticPort) ? parsedStaticPort : 3007;
// ============================================ // ============================================
// File names for userData storage // File names for userData storage

View File

@@ -14,7 +14,12 @@ import { state } from '../state';
export function registerAuthHandlers(): void { export function registerAuthHandlers(): void {
// Get API key for authentication // Get API key for authentication
// Returns null in external server mode to trigger session-based auth // Returns null in external server mode to trigger session-based auth
ipcMain.handle(IPC_CHANNELS.AUTH.GET_API_KEY, () => { // Only returns API key to the main window to prevent leaking to untrusted senders
ipcMain.handle(IPC_CHANNELS.AUTH.GET_API_KEY, (event) => {
// Validate sender is the main window
if (event.sender !== state.mainWindow?.webContents) {
return null;
}
if (state.isExternalServerMode) { if (state.isExternalServerMode) {
return null; return null;
} }

View File

@@ -31,7 +31,7 @@ export function registerDialogHandlers(): void {
? `The selected directory is not allowed. Please select a directory within: ${allowedRoot}` ? `The selected directory is not allowed. Please select a directory within: ${allowedRoot}`
: 'The selected directory is not allowed.'; : 'The selected directory is not allowed.';
await dialog.showErrorBox('Directory Not Allowed', errorMessage); dialog.showErrorBox('Directory Not Allowed', errorMessage);
return { canceled: true, filePaths: [] }; return { canceled: true, filePaths: [] };
} }
@@ -41,16 +41,25 @@ export function registerDialogHandlers(): void {
}); });
// Open file dialog // Open file dialog
ipcMain.handle(IPC_CHANNELS.DIALOG.OPEN_FILE, async (_, options = {}) => { // Filter properties to maintain file-only intent and prevent renderer from requesting directories
if (!state.mainWindow) { ipcMain.handle(
return { canceled: true, filePaths: [] }; IPC_CHANNELS.DIALOG.OPEN_FILE,
async (_, options: Record<string, unknown> = {}) => {
if (!state.mainWindow) {
return { canceled: true, filePaths: [] };
}
// Ensure openFile is always present and filter out directory-related properties
const inputProperties = (options.properties as string[]) ?? [];
const properties = ['openFile', ...inputProperties].filter(
(p) => p !== 'openDirectory' && p !== 'createDirectory'
);
const result = await dialog.showOpenDialog(state.mainWindow, {
...options,
properties: properties as Electron.OpenDialogOptions['properties'],
});
return result;
} }
const result = await dialog.showOpenDialog(state.mainWindow, { );
properties: ['openFile'],
...options,
});
return result;
});
// Save file dialog // Save file dialog
ipcMain.handle(IPC_CHANNELS.DIALOG.SAVE_FILE, async (_, options = {}) => { ipcMain.handle(IPC_CHANNELS.DIALOG.SAVE_FILE, async (_, options = {}) => {

View File

@@ -41,10 +41,10 @@ export function registerShellHandlers(): void {
// URL encode the path to handle special characters (spaces, brackets, etc.) // URL encode the path to handle special characters (spaces, brackets, etc.)
// Handle both Unix (/) and Windows (\) path separators // Handle both Unix (/) and Windows (\) path separators
const normalizedPath = filePath.replace(/\\/g, '/'); const normalizedPath = filePath.replace(/\\/g, '/');
const encodedPath = normalizedPath.startsWith('/') const segments = normalizedPath.split('/').map(encodeURIComponent);
? '/' + normalizedPath.slice(1).split('/').map(encodeURIComponent).join('/') const encodedPath = segments.join('/');
: normalizedPath.split('/').map(encodeURIComponent).join('/'); // VS Code URL format requires a leading slash after 'file'
let url = `vscode://file${encodedPath}`; let url = `vscode://file/${encodedPath}`;
if (line !== undefined && line > 0) { if (line !== undefined && line > 0) {
url += `:${line}`; url += `:${line}`;
if (column !== undefined && column > 0) { if (column !== undefined && column > 0) {