anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
501 lines • 18.7 kB
JavaScript
;
/**
* Function Executor for MCP
*
* Secure function execution framework with validation, sandboxing, and monitoring
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FunctionExecutor = void 0;
const events_1 = require("events");
const types_1 = require("../types");
/**
* Function Executor
*/
class FunctionExecutor extends events_1.EventEmitter {
constructor(functionRegistry, authManager, auditLogger, defaultEnvironment = {
sandboxed: true,
timeoutMs: 30000,
memoryLimitMB: 100,
networkAccess: false,
fileSystemAccess: false,
allowedModules: ['crypto', 'util', 'querystring'],
environmentVariables: {}
}) {
super();
this.functionRegistry = functionRegistry;
this.authManager = authManager;
this.auditLogger = auditLogger;
this.defaultEnvironment = defaultEnvironment;
this.executionQueue = [];
this.activeExecutions = new Map();
this.resultValidators = new Map();
this.securityPolicies = new Map();
this.isProcessing = false;
this.startQueueProcessor();
}
/**
* Execute function with full security and validation
*/
async executeFunction(functionCall, context, options = {
priority: 'normal',
retryOnFailure: true,
maxRetries: 3,
validateResult: true,
auditExecution: true
}) {
// Pre-execution security checks
await this.performSecurityChecks(functionCall, context);
// For critical priority, execute immediately
if (options.priority === 'critical') {
return this.executeImmediate(functionCall, context, options);
}
// Otherwise, queue for execution
return this.queueExecution(functionCall, context, options);
}
/**
* Register result validator for function
*/
registerResultValidator(functionName, schema) {
this.resultValidators.set(functionName, schema);
}
/**
* Set security policy for function
*/
setSecurityPolicy(functionName, policy) {
this.securityPolicies.set(functionName, policy);
}
/**
* Get execution statistics
*/
getExecutionStatistics() {
// This would be tracked in a real implementation
return {
queueSize: this.executionQueue.length,
activeExecutions: this.activeExecutions.size,
totalExecuted: 0,
averageExecutionTime: 0,
errorRate: 0,
securityViolations: 0
};
}
/**
* Perform security checks before execution
*/
async performSecurityChecks(functionCall, context) {
// Check if function exists and is enabled
const definition = this.functionRegistry.getFunctionDefinition(functionCall.name);
if (!definition) {
throw new types_1.MCPError({
code: types_1.MCPErrorCode.FUNCTION_NOT_FOUND,
message: `Function ${functionCall.name} not found`,
timestamp: new Date(),
retryable: false
});
}
// Get security policy
const security = this.securityPolicies.get(functionCall.name) || {
requiredScopes: [],
riskLevel: types_1.FunctionRiskLevel.LOW,
auditRequired: true,
approvalRequired: false
};
// Check required scopes
if (security.requiredScopes.length > 0) {
const authResult = await this.authManager.authorize(context.agentDID, `function:${functionCall.name}`, 'execute');
if (!authResult.authorized) {
throw new types_1.MCPError({
code: types_1.MCPErrorCode.FORBIDDEN,
message: `Insufficient permissions for function ${functionCall.name}`,
timestamp: new Date(),
retryable: false
});
}
}
// Check approval requirement for high-risk functions
if (security.approvalRequired && security.riskLevel === types_1.FunctionRiskLevel.HIGH) {
// In a real implementation, this would check for approval
this.emit('approval_required', {
functionCall,
context,
security
});
}
// Audit function call if required
if (security.auditRequired) {
await this.auditLogger.logRequest({
id: context.requestId,
type: 'function_call',
prompt: `Function call: ${functionCall.name}`,
agentDID: context.agentDID,
sessionId: context.sessionId,
metadata: {
agentDID: context.agentDID,
sessionId: context.sessionId,
requestId: context.requestId,
timestamp: new Date(),
source: 'function-executor',
priority: 'medium',
functionName: functionCall.name
}
}, context.agentDID, context.sessionId);
}
}
/**
* Execute function immediately
*/
async executeImmediate(functionCall, context, options) {
const executionId = `exec-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
try {
// Create execution environment
const environment = this.createExecutionEnvironment(functionCall.name, options.environment);
// Start execution with monitoring
const result = await this.executeWithMonitoring(executionId, functionCall, context, environment, options);
// Validate result if required
if (options.validateResult) {
await this.validateExecutionResult(functionCall.name, result);
}
return result;
}
catch (error) {
// Handle retry logic
if (options.retryOnFailure && options.maxRetries > 0) {
const retryOptions = { ...options, maxRetries: options.maxRetries - 1 };
return this.executeImmediate(functionCall, context, retryOptions);
}
throw error;
}
}
/**
* Queue execution for later processing
*/
async queueExecution(functionCall, context, options) {
return new Promise((resolve, reject) => {
const queueItem = {
id: `queue-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
functionCall,
context,
options,
resolve,
reject,
queuedAt: new Date(),
priority: this.getPriorityValue(options.priority)
};
// Insert in priority order
const insertIndex = this.executionQueue.findIndex(item => item.priority < queueItem.priority);
if (insertIndex === -1) {
this.executionQueue.push(queueItem);
}
else {
this.executionQueue.splice(insertIndex, 0, queueItem);
}
this.emit('function_queued', {
functionName: functionCall.name,
queuePosition: insertIndex === -1 ? this.executionQueue.length : insertIndex + 1,
queueSize: this.executionQueue.length
});
});
}
/**
* Execute function with comprehensive monitoring
*/
async executeWithMonitoring(executionId, functionCall, context, environment, options) {
const startTime = Date.now();
const metrics = {
functionName: functionCall.name,
executionTime: 0,
memoryUsed: 0,
cpuTime: 0,
networkCalls: 0,
errors: [],
warnings: [],
securityViolations: []
};
try {
// Set up timeout
const timeout = setTimeout(() => {
this.activeExecutions.delete(executionId);
throw new types_1.MCPError({
code: types_1.MCPErrorCode.FUNCTION_TIMEOUT,
message: `Function ${functionCall.name} timed out after ${environment.timeoutMs}ms`,
timestamp: new Date(),
retryable: true
});
}, environment.timeoutMs);
this.activeExecutions.set(executionId, timeout);
// Create sandboxed execution context if required
let executionResult;
if (environment.sandboxed) {
executionResult = await this.executeSandboxed(functionCall, context, environment, metrics);
}
else {
executionResult = await this.functionRegistry.executeFunction(functionCall, context);
}
// Clear timeout
clearTimeout(timeout);
this.activeExecutions.delete(executionId);
// Update metrics
metrics.executionTime = Date.now() - startTime;
// Emit monitoring events
this.emit('function_executed', {
executionId,
functionCall,
result: executionResult,
metrics
});
return executionResult;
}
catch (error) {
// Clear timeout
const timeout = this.activeExecutions.get(executionId);
if (timeout) {
clearTimeout(timeout);
this.activeExecutions.delete(executionId);
}
metrics.executionTime = Date.now() - startTime;
metrics.errors.push(error.message);
this.emit('function_error', {
executionId,
functionCall,
error,
metrics
});
throw error;
}
}
/**
* Execute function in sandbox
*/
async executeSandboxed(functionCall, context, environment, metrics) {
// In a real implementation, this would use actual sandboxing
// For now, we'll simulate sandbox constraints and monitoring
const violations = [];
// Monitor memory usage (simulated)
const initialMemory = process.memoryUsage();
try {
// Execute with constraints
const result = await this.functionRegistry.executeFunction(functionCall, context);
// Check memory usage
const finalMemory = process.memoryUsage();
const memoryUsed = (finalMemory.heapUsed - initialMemory.heapUsed) / 1024 / 1024; // MB
if (memoryUsed > environment.memoryLimitMB) {
violations.push({
type: 'memory',
message: `Function exceeded memory limit: ${memoryUsed}MB > ${environment.memoryLimitMB}MB`,
timestamp: new Date(),
severity: 'high'
});
}
metrics.memoryUsed = memoryUsed;
// Report violations
if (violations.length > 0) {
violations.forEach(violation => {
metrics.securityViolations.push(violation.message);
});
this.emit('sandbox_violation', {
functionName: functionCall.name,
violations
});
}
return result;
}
catch (error) {
// Handle sandbox-specific errors
if (error.message.includes('MODULE_NOT_FOUND')) {
violations.push({
type: 'module',
message: 'Attempted to access unauthorized module',
timestamp: new Date(),
severity: 'critical'
});
}
throw error;
}
}
/**
* Validate execution result
*/
async validateExecutionResult(functionName, result) {
const schema = this.resultValidators.get(functionName);
if (!schema) {
return; // No validation schema defined
}
const validation = this.validateResultAgainstSchema(result.result, schema);
if (!validation.valid) {
throw new types_1.MCPError({
code: types_1.MCPErrorCode.FUNCTION_ERROR,
message: `Function result validation failed: ${validation.errors.join(', ')}`,
timestamp: new Date(),
retryable: false
});
}
// Add validation warnings to result metadata
if (validation.warnings.length > 0) {
result.metadata.warnings = (result.metadata.warnings || []).concat(validation.warnings);
}
}
/**
* Validate result against schema
*/
validateResultAgainstSchema(result, schema) {
const errors = [];
const warnings = [];
// Type validation
if (schema.type && typeof result !== schema.type) {
if (schema.type === 'array' && !Array.isArray(result)) {
errors.push(`Expected array, got ${typeof result}`);
}
else if (schema.type === 'object' && (typeof result !== 'object' || result === null || Array.isArray(result))) {
errors.push(`Expected object, got ${typeof result}`);
}
}
// String validation
if (schema.type === 'string' && typeof result === 'string') {
if (schema.maxLength && result.length > schema.maxLength) {
errors.push(`String length ${result.length} exceeds maximum ${schema.maxLength}`);
}
if (schema.pattern && !new RegExp(schema.pattern).test(result)) {
errors.push(`String does not match pattern ${schema.pattern}`);
}
}
// Array validation
if (schema.type === 'array' && Array.isArray(result)) {
if (schema.maxItems && result.length > schema.maxItems) {
errors.push(`Array length ${result.length} exceeds maximum ${schema.maxItems}`);
}
}
// Object validation
if (schema.type === 'object' && typeof result === 'object' && result !== null) {
if (schema.required) {
for (const requiredProp of schema.required) {
if (!(requiredProp in result)) {
errors.push(`Missing required property: ${requiredProp}`);
}
}
}
if (schema.properties) {
for (const [prop, value] of Object.entries(result)) {
if (!schema.properties[prop]) {
warnings.push(`Unexpected property: ${prop}`);
}
}
}
}
return {
valid: errors.length === 0,
errors,
warnings
};
}
/**
* Create execution environment
*/
createExecutionEnvironment(functionName, overrides) {
// Get function-specific security settings
const security = this.securityPolicies.get(functionName);
let environment = { ...this.defaultEnvironment };
// Apply security-based environment settings
if (security) {
switch (security.riskLevel) {
case types_1.FunctionRiskLevel.HIGH:
case types_1.FunctionRiskLevel.CRITICAL:
environment.networkAccess = false;
environment.fileSystemAccess = false;
environment.timeoutMs = Math.min(environment.timeoutMs, 10000); // 10 seconds max
environment.memoryLimitMB = Math.min(environment.memoryLimitMB, 50); // 50MB max
break;
case types_1.FunctionRiskLevel.MEDIUM:
environment.networkAccess = false;
environment.timeoutMs = Math.min(environment.timeoutMs, 20000); // 20 seconds max
break;
case types_1.FunctionRiskLevel.LOW:
// Use defaults
break;
}
}
// Apply overrides
if (overrides) {
environment = { ...environment, ...overrides };
}
return environment;
}
/**
* Get priority value for sorting
*/
getPriorityValue(priority) {
switch (priority) {
case 'critical': return 4;
case 'high': return 3;
case 'normal': return 2;
case 'low': return 1;
default: return 2;
}
}
/**
* Start queue processor
*/
startQueueProcessor() {
this.processInterval = setInterval(() => {
this.processQueue();
}, 100); // Process every 100ms
}
/**
* Process execution queue
*/
async processQueue() {
if (this.isProcessing || this.executionQueue.length === 0) {
return;
}
// Limit concurrent executions
const maxConcurrent = 10;
if (this.activeExecutions.size >= maxConcurrent) {
return;
}
this.isProcessing = true;
try {
const item = this.executionQueue.shift();
if (item) {
// Execute the queued function
this.executeImmediate(item.functionCall, item.context, item.options)
.then(result => item.resolve(result))
.catch(error => item.reject(error));
}
}
finally {
this.isProcessing = false;
}
}
/**
* Clear execution queue
*/
clearQueue() {
for (const item of this.executionQueue) {
item.reject(new types_1.MCPError({
code: types_1.MCPErrorCode.FUNCTION_ERROR,
message: 'Execution queue cleared',
timestamp: new Date(),
retryable: false
}));
}
this.executionQueue = [];
}
/**
* Shutdown executor
*/
shutdown() {
// Clear timers
if (this.processInterval) {
clearInterval(this.processInterval);
}
// Cancel active executions
for (const [id, timeout] of this.activeExecutions) {
clearTimeout(timeout);
}
this.activeExecutions.clear();
// Clear queue
this.clearQueue();
this.removeAllListeners();
}
}
exports.FunctionExecutor = FunctionExecutor;
exports.default = FunctionExecutor;
//# sourceMappingURL=function-executor.js.map