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
JavaScript
"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();