UNPKG

mcp-quiz-server

Version:

🧠 AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.

392 lines (391 loc) • 14.8 kB
"use strict"; /** * @fileoverview Secure Tool Executor - Integrated Sandboxing System * @version 1.0.0 * @since 2025-08-02 * @lastUpdated 2025-08-02 * @module SecureToolExecutor * @description Main execution wrapper that integrates sandbox manager, audit logging, * and security controls for MCP tool execution * @contributors Claude Code Agent * @dependencies ./sandbox-manager, ./audit-logger, ../types/mcp-types * @requirements SECURITY_001 (Tool Isolation), SECURITY_004 (Secure Execution) * @testCoverage End-to-end security tests with malicious tool attempts */ Object.defineProperty(exports, "__esModule", { value: true }); exports.secureToolExecutor = exports.SecureToolExecutor = void 0; const events_1 = require("events"); const audit_logger_1 = require("./audit-logger"); const sandbox_manager_1 = require("./sandbox-manager"); /** * Default security policy */ const DEFAULT_SECURITY_POLICY = { globalResourceLimits: { maxCpuTime: 30000, maxMemory: 100 * 1024 * 1024, // 100MB maxFileOps: 100, maxNetworkRequests: 10, maxExecutionTime: 30000, }, defaultCapabilities: { allowDatabase: false, allowFileSystem: false, allowNetwork: false, allowSubprocess: false, allowedPaths: [], allowedHosts: [], }, toolOverrides: { create_quiz: { capabilities: { allowDatabase: true }, }, get_quiz: { capabilities: { allowDatabase: true }, }, list_quizzes: { capabilities: { allowDatabase: true }, }, delete_quiz: { capabilities: { allowDatabase: true }, }, server_management: { capabilities: { allowFileSystem: true, allowNetwork: true, allowedPaths: ['/tmp', '/var/log'], allowedHosts: ['localhost', '127.0.0.1'], }, }, }, rateLimits: { maxExecutionsPerMinute: 60, maxConcurrentExecutions: 10, }, violationResponse: { blockAfterViolations: 5, blockDurationMs: 300000, // 5 minutes }, }; /** * Secure tool executor with comprehensive security controls */ class SecureToolExecutor extends events_1.EventEmitter { constructor(sandbox, audit, securityPolicy) { super(); this.toolRegistry = new Map(); this.activeExecutions = new Map(); this.executionRateTracker = new Map(); this.blockedTools = new Map(); // tool -> block end time this.sandbox = sandbox || sandbox_manager_1.sandboxManager; this.audit = audit || audit_logger_1.auditLogger; this.securityPolicy = { ...DEFAULT_SECURITY_POLICY, ...securityPolicy }; this.setupEventListeners(); } /** * Initialize the secure tool executor */ async initialize() { await this.audit.initialize(); console.log('🔒 Secure Tool Executor initialized'); } /** * Register a tool with security configuration */ registerTool(definition, handler, securityConfig) { const entry = { definition, handler, security: { resourceLimits: { ...this.securityPolicy.globalResourceLimits, ...securityConfig === null || securityConfig === void 0 ? void 0 : securityConfig.resourceLimits, }, capabilities: { ...this.securityPolicy.defaultCapabilities, ...this.getToolCapabilities(definition.name), ...securityConfig === null || securityConfig === void 0 ? void 0 : securityConfig.capabilities, }, riskLevel: (securityConfig === null || securityConfig === void 0 ? void 0 : securityConfig.riskLevel) || 'medium', }, stats: { totalExecutions: 0, successfulExecutions: 0, failedExecutions: 0, totalExecutionTime: 0, averageExecutionTime: 0, securityViolations: 0, }, }; this.toolRegistry.set(definition.name, entry); console.log(`🔧 Registered secure tool: ${definition.name} (${entry.security.riskLevel} risk)`); } /** * Execute a tool securely */ async executeTool(toolName, args, config) { const startTime = Date.now(); const executionId = this.generateExecutionId(); // Create execution context const context = { executionId, toolName, userId: config === null || config === void 0 ? void 0 : config.userId, requestId: config === null || config === void 0 ? void 0 : config.requestId, startTime, config: config || {}, }; this.activeExecutions.set(executionId, context); try { // Pre-execution security checks await this.performPreExecutionChecks(toolName, context); // Get tool registry entry const toolEntry = this.toolRegistry.get(toolName); if (!toolEntry) { throw new Error(`Tool '${toolName}' is not registered`); } // Log execution start await this.audit.logToolExecutionStarted(executionId, toolName, args, context.userId); // Prepare resource limits and capabilities const resourceLimits = { ...toolEntry.security.resourceLimits, ...config === null || config === void 0 ? void 0 : config.resourceLimits, }; const capabilities = { ...toolEntry.security.capabilities, ...config === null || config === void 0 ? void 0 : config.securityCapabilities, }; // Execute in sandbox const sandboxResult = await this.sandbox.executeTool(toolName, toolEntry.handler, args, resourceLimits, capabilities); // Update statistics this.updateToolStats(toolName, true, Date.now() - startTime); // Log successful execution await this.audit.logToolExecutionCompleted(executionId, toolName, sandboxResult.metadata.executionTime, sandboxResult.metadata.resourceUsage, context.userId); const result = { result: sandboxResult.result, success: true, metadata: { executionId, toolName, executionTime: sandboxResult.metadata.executionTime, resourceUsage: sandboxResult.metadata.resourceUsage, securityEvents: sandboxResult.metadata.securityViolations, performanceMetrics: sandboxResult.metadata.performanceMetrics, }, }; this.emit('toolExecutionCompleted', result); return result; } catch (error) { // Handle execution error const errorMessage = error instanceof Error ? error.message : 'Unknown error'; // Update statistics this.updateToolStats(toolName, false, Date.now() - startTime); // Log failed execution await this.audit.logToolExecutionFailed(executionId, toolName, error instanceof Error ? error : new Error(errorMessage), context.userId); // Handle security violations if (this.isSecurityViolation(error)) { await this.handleSecurityViolation(toolName, error); } const result = { result: { content: [ { type: 'text', text: `Tool execution failed: ${errorMessage}`, }, ], }, success: false, metadata: { executionId, toolName, executionTime: Date.now() - startTime, resourceUsage: { cpuTime: 0, memoryUsed: 0, fileOps: 0, networkRequests: 0, }, securityEvents: this.isSecurityViolation(error) ? [errorMessage] : [], performanceMetrics: { memoryPeak: 0, cpuPeak: 0, }, }, error: errorMessage, }; this.emit('toolExecutionFailed', result); return result; } finally { this.activeExecutions.delete(executionId); } } /** * Get tool information */ getToolInfo(toolName) { return this.toolRegistry.get(toolName); } /** * Get all registered tools */ getAllTools() { return Array.from(this.toolRegistry.values()); } /** * Get execution statistics */ getExecutionStats() { let totalExecutions = 0; let successfulExecutions = 0; let totalExecutionTime = 0; let securityViolations = 0; for (const entry of this.toolRegistry.values()) { totalExecutions += entry.stats.totalExecutions; successfulExecutions += entry.stats.successfulExecutions; totalExecutionTime += entry.stats.totalExecutionTime; securityViolations += entry.stats.securityViolations; } return { activeExecutions: this.activeExecutions.size, totalTools: this.toolRegistry.size, totalExecutions, successRate: totalExecutions > 0 ? successfulExecutions / totalExecutions : 0, averageExecutionTime: totalExecutions > 0 ? totalExecutionTime / totalExecutions : 0, securityViolations, }; } /** * Update security policy */ updateSecurityPolicy(policy) { this.securityPolicy = { ...this.securityPolicy, ...policy }; console.log('🔒 Security policy updated'); } /** * Perform pre-execution security checks */ async performPreExecutionChecks(toolName, context) { // Check if tool is blocked const blockEndTime = this.blockedTools.get(toolName); if (blockEndTime && Date.now() < blockEndTime) { throw new Error(`Tool '${toolName}' is temporarily blocked due to security violations`); } // Check rate limits this.checkRateLimits(toolName); // Check concurrent execution limits if (this.activeExecutions.size >= this.securityPolicy.rateLimits.maxConcurrentExecutions) { throw new Error('Maximum concurrent executions limit reached'); } // Tool-specific security checks can be added here } /** * Check rate limits for tool execution */ checkRateLimits(toolName) { const now = Date.now(); const windowMs = 60000; // 1 minute // Get or create rate tracker for this tool let executions = this.executionRateTracker.get(toolName) || []; // Remove old executions outside the window executions = executions.filter(time => now - time < windowMs); // Check if limit exceeded if (executions.length >= this.securityPolicy.rateLimits.maxExecutionsPerMinute) { throw new Error(`Rate limit exceeded for tool '${toolName}'`); } // Add current execution executions.push(now); this.executionRateTracker.set(toolName, executions); } /** * Get tool-specific capabilities from policy */ getToolCapabilities(toolName) { var _a; return ((_a = this.securityPolicy.toolOverrides[toolName]) === null || _a === void 0 ? void 0 : _a.capabilities) || {}; } /** * Update tool execution statistics */ updateToolStats(toolName, success, executionTime) { const entry = this.toolRegistry.get(toolName); if (!entry) return; entry.stats.totalExecutions++; entry.stats.totalExecutionTime += executionTime; entry.stats.averageExecutionTime = entry.stats.totalExecutionTime / entry.stats.totalExecutions; if (success) { entry.stats.successfulExecutions++; } else { entry.stats.failedExecutions++; } } /** * Check if error is a security violation */ isSecurityViolation(error) { const securityKeywords = [ 'limit exceeded', 'access denied', 'unauthorized', 'violation', 'blocked', 'restricted', ]; const errorMessage = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase(); return securityKeywords.some(keyword => errorMessage.includes(keyword)); } /** * Handle security violation */ async handleSecurityViolation(toolName, error) { const entry = this.toolRegistry.get(toolName); if (entry) { entry.stats.securityViolations++; // Block tool if too many violations if (entry.stats.securityViolations >= this.securityPolicy.violationResponse.blockAfterViolations) { const blockEndTime = Date.now() + this.securityPolicy.violationResponse.blockDurationMs; this.blockedTools.set(toolName, blockEndTime); console.warn(`🚨 Tool '${toolName}' blocked until ${new Date(blockEndTime).toISOString()}`); } } } /** * Setup event listeners */ setupEventListeners() { this.sandbox.on('securityViolation', async (violation) => { await this.audit.logSecurityViolation(violation); }); this.sandbox.on('executionError', event => { console.error(`Sandbox execution error: ${event.executionId}`, event.error); }); } /** * Generate unique execution ID */ generateExecutionId() { return `exec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Cleanup resources */ async cleanup() { // Stop rate limit tracking this.executionRateTracker.clear(); this.blockedTools.clear(); this.activeExecutions.clear(); // Cleanup audit logger await this.audit.cleanup(); // Cleanup sandbox this.sandbox.cleanup(); this.removeAllListeners(); console.log('🔒 Secure Tool Executor cleaned up'); } } exports.SecureToolExecutor = SecureToolExecutor; // Export singleton instance exports.secureToolExecutor = new SecureToolExecutor();