UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

158 lines (157 loc) 6.22 kB
import * as path from 'path'; import logger from '../../../logger.js'; import { getPathResolver } from '../utils/path-resolver.js'; export class VibeTaskManagerSecurityValidator { static instance; normalizedReadDir; normalizedWriteDir; constructor(readDir, writeDir) { const pathResolver = getPathResolver(); const allowedReadDir = readDir || pathResolver.getReadDirectory(); const allowedWriteDir = writeDir || pathResolver.getOutputDirectory(); this.normalizedReadDir = path.resolve(allowedReadDir); this.normalizedWriteDir = path.resolve(allowedWriteDir); logger.debug({ readDir: this.normalizedReadDir, writeDir: this.normalizedWriteDir }, 'VibeTaskManagerSecurityValidator initialized'); } static getInstance(readDir, writeDir) { if (!VibeTaskManagerSecurityValidator.instance) { VibeTaskManagerSecurityValidator.instance = new VibeTaskManagerSecurityValidator(readDir, writeDir); } return VibeTaskManagerSecurityValidator.instance; } isPathWithinReadDirectory(filePath) { try { const normalizedPath = path.resolve(filePath); return this.isPathWithin(normalizedPath, this.normalizedReadDir); } catch (error) { logger.error({ err: error, filePath }, 'Error checking if path is within read directory'); return false; } } isPathWithinWriteDirectory(filePath) { try { const normalizedPath = path.resolve(filePath); return this.isPathWithin(normalizedPath, this.normalizedWriteDir); } catch (error) { logger.error({ err: error, filePath }, 'Error checking if path is within write directory'); return false; } } createSecureReadPath(filePath) { if (!this.isPathWithinReadDirectory(filePath)) { const error = `Security violation: Path '${filePath}' is outside the allowed read directory '${this.normalizedReadDir}'`; logger.error({ filePath, readDir: this.normalizedReadDir }, error); throw new Error(error); } return path.resolve(filePath); } createSecureWritePath(filePath) { if (!this.isPathWithinWriteDirectory(filePath)) { const error = `Security violation: Path '${filePath}' is outside the allowed write directory '${this.normalizedWriteDir}'`; logger.error({ filePath, writeDir: this.normalizedWriteDir }, error); throw new Error(error); } return path.resolve(filePath); } validateReadPath(filePath) { try { if (!filePath || typeof filePath !== 'string') { return { isValid: false, error: 'Invalid path: path must be a non-empty string', violationType: 'invalid_path' }; } if (filePath.includes('..')) { return { isValid: false, error: 'Path traversal detected in read path', violationType: 'path_traversal' }; } if (!this.isPathWithinReadDirectory(filePath)) { return { isValid: false, error: `Path is outside allowed read directory: ${this.normalizedReadDir}`, violationType: 'outside_boundary' }; } return { isValid: true, securePath: path.resolve(filePath) }; } catch (error) { return { isValid: false, error: `Path validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`, violationType: 'invalid_path' }; } } validateWritePath(filePath) { try { if (!filePath || typeof filePath !== 'string') { return { isValid: false, error: 'Invalid path: path must be a non-empty string', violationType: 'invalid_path' }; } if (filePath.includes('..')) { return { isValid: false, error: 'Path traversal detected in write path', violationType: 'path_traversal' }; } if (!this.isPathWithinWriteDirectory(filePath)) { return { isValid: false, error: `Path is outside allowed write directory: ${this.normalizedWriteDir}`, violationType: 'outside_boundary' }; } return { isValid: true, securePath: path.resolve(filePath) }; } catch (error) { return { isValid: false, error: `Path validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`, violationType: 'invalid_path' }; } } getReadDirectory() { return this.normalizedReadDir; } getWriteDirectory() { return this.normalizedWriteDir; } isPathWithin(childPath, parentPath) { const normalizedChild = path.normalize(childPath); const normalizedParent = path.normalize(parentPath); return normalizedChild === normalizedParent || (normalizedChild.startsWith(normalizedParent) && normalizedChild.substring(normalizedParent.length, normalizedParent.length + 1) === path.sep); } } export function getVibeTaskManagerSecurityValidator(readDir, writeDir) { return VibeTaskManagerSecurityValidator.getInstance(readDir, writeDir); } export function createSecureReadPath(filePath) { const validator = getVibeTaskManagerSecurityValidator(); return validator.createSecureReadPath(filePath); } export function createSecureWritePath(filePath) { const validator = getVibeTaskManagerSecurityValidator(); return validator.createSecureWritePath(filePath); }