From 5a73a30a92465a61795e1531619b3d86bca4f15c Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Mon, 20 Oct 2025 14:12:18 +0200 Subject: [PATCH] fix(server): enforce numeric ssh_port end-to-end; harden UI input; coerce in API/DB; fix runtime handler import --- server.js | 13 ++++++++++++- src/app/_components/ServerForm.tsx | 23 ++++++++++++++++++++--- src/server/database-prisma.ts | 6 ++++-- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/server.js b/server.js index 3612f41..112e780 100644 --- a/server.js +++ b/server.js @@ -8,7 +8,18 @@ import stripAnsi from 'strip-ansi'; import { spawn as ptySpawn } from 'node-pty'; import { getSSHExecutionService } from './src/server/ssh-execution-service.js'; import { getDatabase } from './src/server/database-prisma.js'; -import { registerGlobalErrorHandlers } from './src/server/logging/globalHandlers.js'; +// Fallback minimal global error handlers for Node runtime (avoid TS import) +function registerGlobalErrorHandlers() { + if (registerGlobalErrorHandlers._registered) return; + registerGlobalErrorHandlers._registered = true; + process.on('uncaughtException', (err) => { + console.error('uncaught_exception', err); + }); + process.on('unhandledRejection', (reason) => { + console.error('unhandled_rejection', reason); + }); +} +registerGlobalErrorHandlers._registered = false; const dev = process.env.NODE_ENV !== 'production'; const hostname = '0.0.0.0'; diff --git a/src/app/_components/ServerForm.tsx b/src/app/_components/ServerForm.tsx index 33a80ec..86ab171 100644 --- a/src/app/_components/ServerForm.tsx +++ b/src/app/_components/ServerForm.tsx @@ -122,7 +122,21 @@ export function ServerForm({ onSubmit, initialData, isEditing = false, onCancel const handleChange = (field: keyof CreateServerData) => ( e: React.ChangeEvent ) => { - setFormData(prev => ({ ...prev, [field]: e.target.value })); + // Special handling for numeric ssh_port: keep it strictly numeric + if (field === 'ssh_port') { + const raw = (e.target as HTMLInputElement).value ?? ''; + const digitsOnly = raw.replace(/\D+/g, ''); + setFormData(prev => ({ + ...prev, + ssh_port: digitsOnly ? parseInt(digitsOnly, 10) : undefined, + })); + if (errors.ssh_port) { + setErrors(prev => ({ ...prev, ssh_port: undefined })); + } + return; + } + + setFormData(prev => ({ ...prev, [field]: (e.target as HTMLInputElement).value })); // Clear error when user starts typing if (errors[field]) { setErrors(prev => ({ ...prev, [field]: undefined })); @@ -246,14 +260,17 @@ export function ServerForm({ onSubmit, initialData, isEditing = false, onCancel {errors.ssh_port &&

{errors.ssh_port}

} diff --git a/src/server/database-prisma.ts b/src/server/database-prisma.ts index 8522467..d6d83d5 100644 --- a/src/server/database-prisma.ts +++ b/src/server/database-prisma.ts @@ -20,6 +20,7 @@ class DatabaseServicePrisma { // Server CRUD operations async createServer(serverData: CreateServerData) { const { name, ip, user, password, auth_type, ssh_key, ssh_key_passphrase, ssh_port, color, key_generated } = serverData; + const normalizedPort = ssh_port !== undefined ? parseInt(String(ssh_port), 10) : 22; let ssh_key_path = null; @@ -38,7 +39,7 @@ class DatabaseServicePrisma { auth_type: auth_type ?? 'password', ssh_key, ssh_key_passphrase, - ssh_port: ssh_port ?? 22, + ssh_port: Number.isNaN(normalizedPort) ? 22 : normalizedPort, ssh_key_path, key_generated: Boolean(key_generated), color, @@ -60,6 +61,7 @@ class DatabaseServicePrisma { async updateServer(id: number, serverData: CreateServerData) { const { name, ip, user, password, auth_type, ssh_key, ssh_key_passphrase, ssh_port, color, key_generated } = serverData; + const normalizedPort = ssh_port !== undefined ? parseInt(String(ssh_port), 10) : undefined; // Get existing server to check for key changes const existingServer = await this.getServerById(id); @@ -109,7 +111,7 @@ class DatabaseServicePrisma { auth_type: auth_type ?? 'password', ssh_key, ssh_key_passphrase, - ssh_port: ssh_port ?? 22, + ssh_port: normalizedPort ?? 22, ssh_key_path, key_generated: key_generated !== undefined ? Boolean(key_generated) : (existingServer?.key_generated ?? false), color,