UNPKG

context-optimizer-mcp-server

Version:

Context optimization tools MCP server for AI coding assistants - compatible with GitHub Copilot, Cursor AI, and other MCP-supporting assistants

211 lines 8.42 kB
"use strict"; /** * Path security validation utilities * * Prevents path traversal attacks and unauthorized file access */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.PathValidator = void 0; const path = __importStar(require("path")); const fs = __importStar(require("fs/promises")); const manager_1 = require("../config/manager"); class PathValidator { /** * Validates a file path against security boundaries * * @param requestedPath - Path to validate * @returns Validation result with resolved path or error */ static async validateFilePath(requestedPath) { try { const config = manager_1.ConfigurationManager.getConfig(); const resolvedPath = path.resolve(requestedPath); // Check against allowed base paths const isAllowed = config.security.allowedBasePaths.some(basePath => { const resolvedBase = path.resolve(basePath); // Use case-insensitive comparison on Windows const normalizedPath = process.platform === 'win32' ? resolvedPath.toLowerCase() : resolvedPath; const normalizedBase = process.platform === 'win32' ? resolvedBase.toLowerCase() : resolvedBase; const basePlusSlash = normalizedBase + path.sep; return normalizedPath.startsWith(basePlusSlash) || normalizedPath === normalizedBase; }); if (!isAllowed) { return { valid: false, error: `Path '${resolvedPath}' is outside allowed directories: ${config.security.allowedBasePaths.join(', ')}` }; } // Check if file exists try { const stats = await fs.stat(resolvedPath); if (!stats.isFile()) { return { valid: false, error: `Path '${resolvedPath}' is not a file` }; } // Check file size if (stats.size > config.security.maxFileSize) { return { valid: false, error: `File '${resolvedPath}' exceeds maximum size limit (${config.security.maxFileSize} bytes)` }; } } catch { return { valid: false, error: `File '${resolvedPath}' does not exist or is not accessible` }; } return { valid: true, resolvedPath }; } catch (error) { return { valid: false, error: `Path validation error: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Validates a working directory path against security boundaries * * @param requestedPath - Directory path to validate * @returns Validation result with resolved path or error */ static async validateWorkingDirectory(requestedPath) { try { const config = manager_1.ConfigurationManager.getConfig(); const resolvedPath = path.resolve(requestedPath); // Check against allowed base paths const isAllowed = config.security.allowedBasePaths.some(basePath => { const resolvedBase = path.resolve(basePath); // Use case-insensitive comparison on Windows const normalizedPath = process.platform === 'win32' ? resolvedPath.toLowerCase() : resolvedPath; const normalizedBase = process.platform === 'win32' ? resolvedBase.toLowerCase() : resolvedBase; const basePlusSlash = normalizedBase + path.sep; return normalizedPath.startsWith(basePlusSlash) || normalizedPath === normalizedBase; }); if (!isAllowed) { return { valid: false, error: `Working directory '${resolvedPath}' is outside allowed paths: ${config.security.allowedBasePaths.join(', ')}` }; } // Check if directory exists try { const stats = await fs.stat(resolvedPath); if (!stats.isDirectory()) { return { valid: false, error: `Path '${resolvedPath}' is not a directory` }; } } catch { return { valid: false, error: `Directory '${resolvedPath}' does not exist or is not accessible` }; } return { valid: true, resolvedPath }; } catch (error) { return { valid: false, error: `Directory validation error: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Helper method to check if a path is within allowed boundaries (without file system checks) * * @param requestedPath - Path to check * @returns Whether the path is within allowed boundaries */ static isPathAllowed(requestedPath) { try { const config = manager_1.ConfigurationManager.getConfig(); const resolvedPath = path.resolve(requestedPath); return config.security.allowedBasePaths.some(basePath => { const resolvedBase = path.resolve(basePath); return resolvedPath.startsWith(resolvedBase + path.sep) || resolvedPath === resolvedBase; }); } catch { return false; } } /** * Normalize and sanitize a path for logging purposes * * @param filePath - Path to sanitize * @returns Sanitized path safe for logging */ static sanitizePathForLogging(filePath) { try { // Check for obviously invalid characters first if (/[<>:"|?*\0]/.test(filePath)) { return '[INVALID_PATH]'; } const resolved = path.resolve(filePath); // Replace all directory components except the last one with placeholders const parts = resolved.split(path.sep); if (parts.length <= 2) { return resolved; // Too short to sanitize meaningfully } // Keep first part (drive on Windows, root on Unix) and last part (filename) const sanitized = parts.map((part, index) => { if (index === 0 || index === parts.length - 1) { return part; } return '****'; }).join(path.sep); return sanitized; } catch { return '[INVALID_PATH]'; } } } exports.PathValidator = PathValidator; //# sourceMappingURL=pathValidator.js.map