From 646b47807595ca48a4a5cfdb8ad5de0bab279855 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 1 Jul 2025 12:48:34 +0100 Subject: [PATCH] fix: Add security validation and sanitization for session titles - Validate session ID format to prevent injection attacks - Sanitize title input: limit to 256 chars, filter control characters - Prevent potential security issues with malformed session titles --- web/src/server/fwd.ts | 24 ++++++++++++++++++++++-- web/src/server/pty/session-manager.ts | 15 +++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/web/src/server/fwd.ts b/web/src/server/fwd.ts index c85c4450..1e1b2e86 100755 --- a/web/src/server/fwd.ts +++ b/web/src/server/fwd.ts @@ -143,6 +143,15 @@ export async function startVibeTunnelForward(args: string[]) { const controlPath = path.join(os.homedir(), '.vibetunnel', 'control'); const sessionManager = new SessionManager(controlPath); + // Validate session ID format for security + if (!/^[a-zA-Z0-9_-]+$/.test(sessionId)) { + logger.error( + 'Invalid session ID format. Only alphanumeric characters, hyphens, and underscores are allowed.' + ); + closeLogger(); + process.exit(1); + } + try { // Load existing session info const sessionInfo = sessionManager.loadSessionInfo(sessionId); @@ -152,11 +161,22 @@ export async function startVibeTunnelForward(args: string[]) { process.exit(1); } + // Sanitize the title - limit length and filter out problematic characters + const sanitizedTitle = updateTitle + .substring(0, 256) // Limit length + .split('') + .filter((char) => { + const code = char.charCodeAt(0); + // Allow printable characters (space to ~) and extended ASCII/Unicode + return code >= 32 && code !== 127 && (code < 128 || code > 159); + }) + .join(''); + // Update the title - sessionInfo.name = updateTitle; + sessionInfo.name = sanitizedTitle; sessionManager.saveSessionInfo(sessionId, sessionInfo); - logger.log(`Session title updated to: ${updateTitle}`); + logger.log(`Session title updated to: ${sanitizedTitle}`); closeLogger(); process.exit(0); } catch (error) { diff --git a/web/src/server/pty/session-manager.ts b/web/src/server/pty/session-manager.ts index 8ef4c18a..49e4d334 100644 --- a/web/src/server/pty/session-manager.ts +++ b/web/src/server/pty/session-manager.ts @@ -19,6 +19,7 @@ const logger = createLogger('session-manager'); export class SessionManager { private controlPath: string; + private static readonly SESSION_ID_REGEX = /^[a-zA-Z0-9_-]+$/; constructor(controlPath?: string) { this.controlPath = controlPath || path.join(os.homedir(), '.vibetunnel', 'control'); @@ -26,6 +27,18 @@ export class SessionManager { this.ensureControlDirectory(); } + /** + * Validate session ID format for security + */ + private validateSessionId(sessionId: string): void { + if (!SessionManager.SESSION_ID_REGEX.test(sessionId)) { + throw new PtyError( + 'Invalid session ID format. Only alphanumeric characters, hyphens, and underscores are allowed.', + 'INVALID_SESSION_ID' + ); + } + } + /** * Ensure the control directory exists */ @@ -45,6 +58,7 @@ export class SessionManager { stdinPath: string; sessionJsonPath: string; } { + this.validateSessionId(sessionId); const controlDir = path.join(this.controlPath, sessionId); // Create session directory @@ -96,6 +110,7 @@ export class SessionManager { * Save session info to JSON file */ saveSessionInfo(sessionId: string, sessionInfo: SessionInfo): void { + this.validateSessionId(sessionId); try { const sessionInfoStr = JSON.stringify(sessionInfo, null, 2);