Skip to main content
Glama
container-executor.ts46 kB
import { spawn, SpawnOptions } from 'child_process'; import { v4 as uuidv4 } from 'uuid'; import { promisify } from 'util'; import { exec } from 'child_process'; import { logger } from '@/utils/logger'; import { redis } from '@/database/redis'; import { vault } from '@/security/vault'; import { Metrics } from '@/monitoring/metrics'; import crypto from 'crypto'; import fs from 'fs/promises'; import path from 'path'; // Security hardening imports import { SeccompProfileManager, SecurityLevel } from './security/seccomp-profiles'; import { UserNamespaceManager } from './security/user-namespace'; import { MandatoryAccessControl } from './security/mac-profiles'; import { ResourceController } from './security/resource-controller'; import { NetworkIsolation } from './security/network-isolation'; import { CapabilityManager } from './security/capability-manager'; const execAsync = promisify(exec); export enum ContainerRuntime { GVISOR = 'gvisor', KATA = 'kata', DOCKER = 'docker', } // SecurityLevel is now imported from seccomp-profiles interface ExecutionConfig { runtime: ContainerRuntime; image: string; command: string[]; env?: Record<string, string>; workDir?: string; timeout?: number; cpuLimit?: string; memoryLimit?: string; network?: boolean; readOnly?: boolean; securityLevel?: SecurityLevel; userId?: string; sessionId?: string; // Enhanced security configuration securityHardening?: SecurityHardeningConfig; } interface SecurityHardeningConfig { enableSeccomp: boolean; enableUserNamespace: boolean; enableMandatoryAccessControl: boolean; enableResourceLimits: boolean; enableNetworkIsolation: boolean; enableCapabilityDropping: boolean; allowedCapabilities?: string[]; networkPolicy?: 'none' | 'bridge' | 'custom'; macProfile?: 'apparmor' | 'selinux' | 'both'; customSeccompProfile?: any; customResourceLimits?: any; } interface ExecutionResult { executionId: string; stdout: string; stderr: string; exitCode: number; duration: number; resourceUsage: { cpuPercent: number; memoryMB: number; networkIO?: { bytesReceived: number; bytesSent: number; }; }; securityEvents: SecurityEvent[]; } interface SecurityEvent { timestamp: Date; type: string; severity: 'low' | 'medium' | 'high' | 'critical'; description: string; details?: any; source: 'seccomp' | 'namespace' | 'mac' | 'resource' | 'network' | 'capability' | 'general'; remediation?: string; } interface SecurityContext { seccompProfileId?: string; userNamespaceId?: string; macProfileId?: string; resourceGroupId?: string; networkNamespaceId?: string; appliedCapabilities: string[]; droppedCapabilities: string[]; securityLevel: SecurityLevel; hardeningEnabled: boolean; } export class ContainerExecutor { private static readonly SANDBOX_BASE = '/var/lib/mcp-sandbox'; private static readonly MAX_OUTPUT_SIZE = 10 * 1024 * 1024; // 10MB private static readonly DEFAULT_TIMEOUT = 30000; // 30 seconds private static readonly CONTAINER_PREFIX = 'mcp-exec'; private executionCount = 0; private activeExecutions = new Map<string, AbortController>(); private activeSecurityContexts = new Map<string, SecurityContext>(); // Security hardening is handled via static methods constructor() { this.initializeSandboxEnvironment(); } private async initializeSandboxEnvironment(): Promise<void> { try { await fs.mkdir(ContainerExecutor.SANDBOX_BASE, { recursive: true }); await this.setupSecurityPolicies(); await this.verifyRuntimeAvailability(); logger.info('Container executor initialized'); } catch (error) { logger.error('Failed to initialize container executor', { error }); throw error; } } private async setupSecurityPolicies(): Promise<void> { const policies = { seccomp: await this.loadSeccompProfile(), apparmor: await this.loadApparmorProfile(), selinux: await this.loadSelinuxPolicy(), }; await vault.write('runtime/security-policies', policies); } private async loadSeccompProfile(): Promise<object> { return { defaultAction: 'SCMP_ACT_ERRNO', architectures: ['SCMP_ARCH_X86_64', 'SCMP_ARCH_AARCH64'], syscalls: [ { names: [ 'accept', 'accept4', 'access', 'alarm', 'bind', 'brk', 'capget', 'capset', 'chdir', 'chmod', 'chown', 'clock_getres', 'clock_gettime', 'clone', 'close', 'connect', 'copy_file_range', 'creat', 'dup', 'dup2', 'dup3', 'epoll_create', 'epoll_create1', 'epoll_ctl', 'epoll_pwait', 'epoll_wait', 'eventfd', 'eventfd2', 'execve', 'execveat', 'exit', 'exit_group', 'faccessat', 'fadvise64', 'fallocate', 'fanotify_mark', 'fchdir', 'fchmod', 'fchmodat', 'fchown', 'fchownat', 'fcntl', 'fdatasync', 'fgetxattr', 'flistxattr', 'flock', 'fork', 'fremovexattr', 'fsync', 'ftruncate', 'futex', 'getcwd', 'getdents', 'getdents64', 'getegid', 'geteuid', 'getgid', 'getgroups', 'getitimer', 'getpeername', 'getpgid', 'getpgrp', 'getpid', 'getppid', 'getpriority', 'getrandom', 'getresgid', 'getresuid', 'getrlimit', 'getrusage', 'getsid', 'getsockname', 'getsockopt', 'gettid', 'gettimeofday', 'getuid', 'getxattr', 'inotify_add_watch', 'inotify_init', 'inotify_init1', 'inotify_rm_watch', 'io_cancel', 'io_destroy', 'io_getevents', 'io_setup', 'io_submit', 'ioctl', 'kill', 'lgetxattr', 'link', 'linkat', 'listen', 'listxattr', 'llistxattr', 'lremovexattr', 'lseek', 'lsetxattr', 'lstat', 'madvise', 'memfd_create', 'mincore', 'mkdir', 'mkdirat', 'mknod', 'mknodat', 'mlock', 'mlockall', 'mmap', 'mprotect', 'mremap', 'msgctl', 'msgget', 'msgrcv', 'msgsnd', 'msync', 'munlock', 'munlockall', 'munmap', 'nanosleep', 'newfstatat', 'open', 'openat', 'pause', 'pipe', 'pipe2', 'poll', 'ppoll', 'prctl', 'pread64', 'preadv', 'preadv2', 'prlimit64', 'pselect6', 'pwrite64', 'pwritev', 'pwritev2', 'read', 'readahead', 'readlink', 'readlinkat', 'readv', 'reboot', 'recv', 'recvfrom', 'recvmmsg', 'recvmsg', 'remap_file_pages', 'removexattr', 'rename', 'renameat', 'renameat2', 'restart_syscall', 'rmdir', 'rt_sigaction', 'rt_sigpending', 'rt_sigprocmask', 'rt_sigqueueinfo', 'rt_sigreturn', 'rt_sigsuspend', 'rt_sigtimedwait', 'rt_tgsigqueueinfo', 'sched_getaffinity', 'sched_getattr', 'sched_getparam', 'sched_getscheduler', 'sched_get_priority_max', 'sched_get_priority_min', 'sched_rr_get_interval', 'sched_setaffinity', 'sched_setattr', 'sched_setparam', 'sched_setscheduler', 'sched_yield', 'seccomp', 'select', 'semctl', 'semget', 'semop', 'semtimedop', 'send', 'sendfile', 'sendmmsg', 'sendmsg', 'sendto', 'setfsgid', 'setfsuid', 'setgid', 'setgroups', 'setitimer', 'setpgid', 'setpriority', 'setregid', 'setresgid', 'setresuid', 'setreuid', 'setrlimit', 'setsid', 'setsockopt', 'setuid', 'setxattr', 'shmat', 'shmctl', 'shmdt', 'shmget', 'shutdown', 'sigaltstack', 'signalfd', 'signalfd4', 'socket', 'socketpair', 'splice', 'stat', 'statfs', 'statx', 'symlink', 'symlinkat', 'sync', 'sync_file_range', 'syncfs', 'sysinfo', 'tee', 'tgkill', 'time', 'timer_create', 'timer_delete', 'timer_getoverrun', 'timer_gettime', 'timer_settime', 'timerfd_create', 'timerfd_gettime', 'timerfd_settime', 'times', 'tkill', 'truncate', 'umask', 'uname', 'unlink', 'unlinkat', 'unshare', 'utime', 'utimensat', 'utimes', 'vfork', 'vmsplice', 'wait4', 'waitid', 'waitpid', 'write', 'writev' ], action: 'SCMP_ACT_ALLOW' } ] }; } private async loadApparmorProfile(): Promise<string> { return ` #include <tunables/global> profile mcp-executor flags=(attach_disconnected,mediate_deleted) { #include <abstractions/base> network inet tcp, network inet udp, network inet icmp, network netlink raw, deny network raw, deny network packet, file, umount, deny /proc/sys/kernel/** wklx, deny /proc/sysrq-trigger rwklx, deny /proc/kcore rwklx, deny /proc/mem rwklx, deny /proc/kmem rwklx, deny mount, deny /sys/[^f]*/** wklx, deny /sys/f[^s]*/** wklx, deny /sys/fs/[^c]*/** wklx, deny /sys/fs/c[^g]*/** wklx, deny /sys/fs/cg[^r]*/** wklx, deny /sys/firmware/** rwklx, deny /sys/kernel/security/** rwklx, ptrace (trace,read) peer=mcp-executor, } `; } private async loadSelinuxPolicy(): Promise<string> { return ` module mcp_executor 1.0; require { type container_t; type container_runtime_t; type sysadm_t; class process { transition signal }; class file { read write execute }; } type mcp_executor_t; type mcp_executor_exec_t; allow container_runtime_t mcp_executor_t:process transition; allow mcp_executor_t container_t:process signal; allow mcp_executor_t self:file { read write execute }; `; } private async verifyRuntimeAvailability(): Promise<void> { const runtimes = [ { name: ContainerRuntime.GVISOR, command: 'runsc --version' }, { name: ContainerRuntime.KATA, command: 'kata-runtime --version' }, { name: ContainerRuntime.DOCKER, command: 'docker --version' }, ]; for (const runtime of runtimes) { try { await execAsync(runtime.command); logger.info(`Container runtime available: ${runtime.name}`); } catch (error) { logger.warn(`Container runtime not available: ${runtime.name}`); } } } public async execute(config: ExecutionConfig): Promise<ExecutionResult> { const executionId = uuidv4(); const startTime = Date.now(); const securityEvents: SecurityEvent[] = []; let securityContext: SecurityContext; try { // Enhanced security validation await this.validateExecutionConfig(config); await this.validateSecurityConfiguration(config); // Apply security hardening securityContext = await this.applySecurityHardening(executionId, config); this.activeSecurityContexts.set(executionId, securityContext); await this.recordExecutionStart(executionId, config); const runtime = await this.selectRuntime(config); const containerName = `${ContainerExecutor.CONTAINER_PREFIX}-${executionId}`; // Enhanced container execution with security context const result = await this.runSecureContainer({ ...config, runtime, containerName, executionId, securityContext }); // Enhanced security scanning await this.performSecurityScanning(executionId, config.image, result); // Collect security metrics const resourceUsage = await this.collectEnhancedResourceMetrics(containerName, securityContext); const securityMetrics = await this.collectSecurityMetrics(executionId, securityContext); // Cleanup with security context await this.cleanupSecureContainer(executionId, containerName, securityContext); const duration = Date.now() - startTime; await this.recordExecutionEnd(executionId, result, duration); return { executionId, stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode, duration, resourceUsage: { ...resourceUsage, ...securityMetrics }, securityEvents: [...securityEvents, ...await this.getSecurityEvents(executionId)], }; } catch (error) { logger.error('Secure container execution failed', { executionId, error }); // Cleanup on failure if (securityContext!) { await this.cleanupSecurityContext(executionId, securityContext); } throw error; } } private async validateExecutionConfig(config: ExecutionConfig): Promise<void> { if (!config.image || !config.command || config.command.length === 0) { throw new Error('Invalid execution configuration'); } const allowedImages = await this.getAllowedImages(); if (!allowedImages.includes(config.image)) { throw new Error(`Image ${config.image} is not in the allowed list`); } if (config.command.some(cmd => this.containsMaliciousPattern(cmd))) { throw new Error('Command contains potentially malicious patterns'); } // Enhanced security validation await this.validateImageSecurity(config.image); await this.validateCommandSecurity(config.command); await this.validateEnvironmentSecurity(config.env || {}); } private async validateSecurityConfiguration(config: ExecutionConfig): Promise<void> { const securityLevel = config.securityLevel || SecurityLevel.HIGH; // Validate security level requirements if (securityLevel === SecurityLevel.CRITICAL) { // Critical level requires all security features const hardening = config.securityHardening || this.getDefaultSecurityHardening(); if (!hardening.enableSeccomp || !hardening.enableUserNamespace || !hardening.enableCapabilityDropping || !hardening.enableNetworkIsolation) { throw new Error('Critical security level requires all security hardening features'); } } // Validate capability configuration if (config.securityHardening?.allowedCapabilities) { const dangerousCapabilities = ['CAP_SYS_ADMIN', 'CAP_SYS_MODULE', 'CAP_SYS_RAWIO', 'CAP_SYS_PTRACE']; const hasDangerous = config.securityHardening.allowedCapabilities.some(cap => dangerousCapabilities.includes(cap) ); if (hasDangerous && securityLevel !== SecurityLevel.LOW) { throw new Error('Dangerous capabilities not allowed for this security level'); } } } private containsMaliciousPattern(command: string): boolean { const maliciousPatterns = [ // File system attacks /rm\s+-rf\s+\//, /dd\s+if=\/dev\/(zero|urandom)\s+of=/, /chmod\s+(777|4755)\s+\//, /chown\s+root\s+/, // Shell injection and reverse shells /:(){ :|:& };:/, // Fork bomb /\bwget\b.*\|\s*sh/, /\bcurl\b.*\|\s*bash/, /\/dev\/tcp\//, /\bnc\b.*-e\s*\/bin\/(sh|bash)/, /\bbash\b.*-i\s*>&\s*\/dev\/tcp/, // Programming language shells /\bpython\b.*-c.*import\s+socket/, /\bperl\b.*-e.*socket/, /\bnode\b.*-e.*require\s*\(\s*['"]net['"]/, /\bruby\b.*-e.*socket/, // Privilege escalation /sudo\s+su\s*-/, /passwd\s+root/, /usermod\s+-a\s+-G\s+sudo/, /setuid\s*\(/, // Container escape attempts /docker\s+run.*--privileged/, /mount\s+.*\/proc/, /nsenter\s+/, /unshare\s+/, /capsh\s+/, // Network reconnaissance /nmap\s+/, /masscan\s+/, /zmap\s+/, // System information gathering /\/proc\/version/, /\/etc\/shadow/, /\/etc\/passwd/, /\/root\//, // Cryptomining indicators /xmrig/, /cpuminer/, /minerd/, // Suspicious network activity /base64\s+-d.*\|\s*sh/, /echo\s+.*\|\s*base64\s+-d/, ]; return maliciousPatterns.some(pattern => pattern.test(command)); } private async getAllowedImages(): Promise<string[]> { const images = await vault.read('runtime/allowed-images'); return images?.data?.images || [ 'alpine:3.19', 'ubuntu:22.04', 'python:3.11-slim', 'node:20-alpine', 'golang:1.21-alpine', 'rust:1.75-alpine', 'openjdk:21-slim', ]; } private async selectRuntime(config: ExecutionConfig): Promise<ContainerRuntime> { if (config.runtime) { return config.runtime; } switch (config.securityLevel) { case SecurityLevel.CRITICAL: return ContainerRuntime.KATA; case SecurityLevel.HIGH: return ContainerRuntime.GVISOR; default: return ContainerRuntime.DOCKER; } } private async runSecureContainer(config: any): Promise<any> { const { runtime, containerName, executionId, image, command, env, workDir, timeout, cpuLimit, memoryLimit, network, readOnly, securityContext } = config; // Build enhanced runtime command with security hardening const runtimeCommand = await this.buildSecureRuntimeCommand(runtime, { containerName, image, command, env, workDir, cpuLimit: cpuLimit || '1000m', memoryLimit: memoryLimit || '1Gi', network: network !== false, readOnly: readOnly !== false, securityContext }); return new Promise((resolve, reject) => { const controller = new AbortController(); this.activeExecutions.set(executionId, controller); let stdout = ''; let stderr = ''; let outputSize = 0; const timeoutId = setTimeout(() => { controller.abort(); reject(new Error('Execution timeout')); }, timeout || ContainerExecutor.DEFAULT_TIMEOUT); const proc = spawn(runtimeCommand.cmd, runtimeCommand.args, { signal: controller.signal, env: { ...process.env, ...env }, // Enhanced security options uid: 1000, // Non-root execution gid: 1000, } as SpawnOptions); // Enhanced output monitoring with security filtering proc.stdout?.on('data', (data) => { outputSize += data.length; if (outputSize > ContainerExecutor.MAX_OUTPUT_SIZE) { controller.abort(); reject(new Error('Output size exceeded limit')); } const filteredData = this.filterSensitiveOutput(data.toString()); stdout += filteredData; }); proc.stderr?.on('data', (data) => { outputSize += data.length; if (outputSize > ContainerExecutor.MAX_OUTPUT_SIZE) { controller.abort(); reject(new Error('Output size exceeded limit')); } const filteredData = this.filterSensitiveOutput(data.toString()); stderr += filteredData; }); proc.on('exit', (code) => { clearTimeout(timeoutId); this.activeExecutions.delete(executionId); resolve({ stdout, stderr, exitCode: code || 0 }); }); proc.on('error', (error) => { clearTimeout(timeoutId); this.activeExecutions.delete(executionId); reject(error); }); }); } private async buildSecureRuntimeCommand(runtime: ContainerRuntime, options: any): Promise<{ cmd: string; args: string[] }> { const securityContext = options.securityContext; switch (runtime) { case ContainerRuntime.GVISOR: return await this.buildGvisorSecureCommand(options); case ContainerRuntime.KATA: return await this.buildKataSecureCommand(options); case ContainerRuntime.DOCKER: default: return await this.buildDockerSecureCommand(options); } } private async buildDockerSecureCommand(options: any): Promise<{ cmd: string; args: string[] }> { const securityContext = options.securityContext; const dockerArgs = [ 'run', '--rm', '--name', options.containerName, '--cpus', options.cpuLimit, '--memory', options.memoryLimit, '--memory-swap', '0', // Disable swap '--oom-kill-disable=false', // Allow OOM killer '--pids-limit', '50', // Strict PID limit '--ulimit', 'nofile=1024:1024', // File descriptor limit '--ulimit', 'core=0', // No core dumps ]; // Enhanced security options dockerArgs.push('--security-opt', 'no-new-privileges:true'); dockerArgs.push('--security-opt', 'apparmor=docker-default'); // Capability management dockerArgs.push('--cap-drop', 'ALL'); if (securityContext.appliedCapabilities.length > 0) { securityContext.appliedCapabilities.forEach(cap => { dockerArgs.push('--cap-add', cap); }); } // Seccomp profile if (securityContext.seccompProfileId) { const profilePath = `/tmp/mcp/seccomp-${securityContext.seccompProfileId}.json`; dockerArgs.push('--security-opt', `seccomp=${profilePath}`); } // User namespace if (securityContext.userNamespaceId) { dockerArgs.push('--userns', 'host'); // Will be managed by our namespace manager dockerArgs.push('--user', '1000:1000'); // Non-root execution } // Network isolation if (securityContext.networkNamespaceId) { dockerArgs.push('--network', 'none'); // Maximum isolation } else if (!options.network) { dockerArgs.push('--network', 'none'); } // Read-only root filesystem if (options.readOnly) { dockerArgs.push('--read-only'); dockerArgs.push('--tmpfs', '/tmp:noexec,nosuid,nodev,size=50M'); dockerArgs.push('--tmpfs', '/var/tmp:noexec,nosuid,nodev,size=10M'); } // Working directory if (options.workDir) { dockerArgs.push('--workdir', options.workDir); } // Environment variables (filtered) if (options.env) { Object.entries(options.env).forEach(([key, value]) => { if (this.isSecureEnvironmentVariable(key, value as string)) { dockerArgs.push('-e', `${key}=${value}`); } }); } // Additional hardening dockerArgs.push('--device-cgroup-rule', 'c *:* m'); // Block all device access by default dockerArgs.push('--kernel-memory', '128m'); // Kernel memory limit dockerArgs.push('--shm-size', '64m'); // Shared memory limit // Prevent privilege escalation dockerArgs.push('--privileged=false'); dockerArgs.push('--init'); // Use tini as init process dockerArgs.push(options.image); dockerArgs.push(...options.command); return { cmd: 'docker', args: dockerArgs }; } private async buildGvisorSecureCommand(options: any): Promise<{ cmd: string; args: string[] }> { const securityContext = options.securityContext; return { cmd: 'runsc', args: [ 'run', '--network=none', '--rootless', '--platform=ptrace', '--overlay', '--cpu-num=1', `--memory=${options.memoryLimit}`, '--file-access=exclusive', // Enhanced file isolation '--network=none', // No network access '--name', options.containerName, options.image, ...options.command, ], }; } private async buildKataSecureCommand(options: any): Promise<{ cmd: string; args: string[] }> { const securityContext = options.securityContext; return { cmd: 'kata-runtime', args: [ 'run', '--runtime=io.containerd.kata.v2', '--cpu', '1', '--memory', options.memoryLimit, '--name', options.containerName, options.image, ...options.command, ], }; } private async collectResourceMetrics(containerName: string): Promise<any> { try { const { stdout } = await execAsync(`docker stats ${containerName} --no-stream --format json`); const stats = JSON.parse(stdout); return { cpuPercent: parseFloat(stats.CPUPerc?.replace('%', '') || '0'), memoryMB: this.parseMemory(stats.MemUsage), networkIO: { bytesReceived: this.parseBytes(stats.NetIO?.split('/')[0]), bytesSent: this.parseBytes(stats.NetIO?.split('/')[1]), }, }; } catch (error) { logger.warn('Failed to collect resource metrics', { containerName, error }); return { cpuPercent: 0, memoryMB: 0, }; } } private parseMemory(memUsage: string): number { if (!memUsage) return 0; const match = memUsage.match(/([\d.]+)\s*([KMGT]i?B)/i); if (!match) return 0; const value = parseFloat(match[1]); const unit = match[2].toUpperCase(); const multipliers: Record<string, number> = { 'B': 1 / (1024 * 1024), 'KB': 1 / 1024, 'KIB': 1 / 1024, 'MB': 1, 'MIB': 1, 'GB': 1024, 'GIB': 1024, 'TB': 1024 * 1024, 'TIB': 1024 * 1024, }; return value * (multipliers[unit] || 1); } private parseBytes(bytesStr: string): number { if (!bytesStr) return 0; const match = bytesStr.match(/([\d.]+)\s*([KMGT]?B)/i); if (!match) return 0; const value = parseFloat(match[1]); const unit = match[2].toUpperCase(); const multipliers: Record<string, number> = { 'B': 1, 'KB': 1024, 'MB': 1024 * 1024, 'GB': 1024 * 1024 * 1024, 'TB': 1024 * 1024 * 1024 * 1024, }; return value * (multipliers[unit] || 1); } private async cleanupSecureContainer(executionId: string, containerName: string, securityContext: SecurityContext): Promise<void> { try { // Stop and remove container await execAsync(`docker stop ${containerName} 2>/dev/null || true`); await execAsync(`docker rm ${containerName} 2>/dev/null || true`); // Cleanup security context await this.cleanupSecurityContext(executionId, securityContext); } catch (error) { logger.warn('Secure container cleanup failed', { containerName, executionId, error }); } } private async cleanupSecurityContext(executionId: string, securityContext: SecurityContext): Promise<void> { try { // Cleanup user namespace if (securityContext.userNamespaceId) { await UserNamespaceManager.destroyNamespace(executionId); } // Cleanup network namespace if (securityContext.networkNamespaceId) { await NetworkIsolation.destroyNetworkNamespace(executionId); } // Cleanup resource group if (securityContext.resourceGroupId) { await ResourceController.destroyResourceGroup(executionId); } // Cleanup seccomp profile file if (securityContext.seccompProfileId) { try { await fs.unlink(`/tmp/mcp/seccomp-${securityContext.seccompProfileId}.json`); } catch { // File may not exist } } // Remove from active contexts this.activeSecurityContexts.delete(executionId); logger.info('Security context cleaned up', { executionId }); } catch (error) { logger.error('Failed to cleanup security context', { executionId, error }); throw error; } } private async scanForVulnerabilities(executionId: string, image: string): Promise<void> { try { const { stdout } = await execAsync(`trivy image --format json --quiet ${image}`); const vulnerabilities = JSON.parse(stdout); if (vulnerabilities.Results?.length > 0) { const criticalVulns = vulnerabilities.Results.flatMap((r: any) => r.Vulnerabilities?.filter((v: any) => v.Severity === 'CRITICAL') || [] ); if (criticalVulns.length > 0) { logger.warn('Critical vulnerabilities detected in image', { executionId, image, vulnerabilities: criticalVulns.slice(0, 5), }); } } } catch (error) { logger.error('Vulnerability scanning failed', { executionId, image, error }); } } private async performDLPScan(content: string): Promise<void> { const patterns = [ { name: 'SSN', regex: /\b\d{3}-\d{2}-\d{4}\b/g }, { name: 'Credit Card', regex: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g }, { name: 'API Key', regex: /\b[A-Za-z0-9]{32,}\b/g }, { name: 'Private Key', regex: /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/g }, { name: 'AWS Access Key', regex: /AKIA[0-9A-Z]{16}/g }, ]; for (const pattern of patterns) { const matches = content.match(pattern.regex); if (matches && matches.length > 0) { logger.warn('Potential sensitive data detected', { type: pattern.name, count: matches.length, }); } } } private async recordExecutionStart(executionId: string, config: ExecutionConfig): Promise<void> { const record = { executionId, startTime: new Date().toISOString(), userId: config.userId, sessionId: config.sessionId, image: config.image, command: config.command.join(' '), runtime: config.runtime, securityLevel: config.securityLevel, }; await redis.hset(`execution:${executionId}`, record); await redis.expire(`execution:${executionId}`, 86400); // 24 hours // Metrics will be recorded in recordExecutionEnd with actual duration } private async recordExecutionEnd(executionId: string, result: any, duration: number): Promise<void> { await redis.hset(`execution:${executionId}`, { endTime: new Date().toISOString(), exitCode: result.exitCode, duration, outputSize: (result.stdout.length + result.stderr.length), }); Metrics.recordToolExecution('container-executor', 'success', duration, 'system'); } public async terminateExecution(executionId: string): Promise<void> { const controller = this.activeExecutions.get(executionId); if (controller) { controller.abort(); this.activeExecutions.delete(executionId); logger.info('Execution terminated', { executionId }); } } public async getActiveExecutions(): Promise<string[]> { return Array.from(this.activeExecutions.keys()); } // ============= SECURITY HARDENING METHODS ============= /** * Applies comprehensive security hardening to container execution */ private async applySecurityHardening(executionId: string, config: ExecutionConfig): Promise<SecurityContext> { const securityLevel = config.securityLevel || SecurityLevel.HIGH; const hardening = config.securityHardening || this.getDefaultSecurityHardening(); const securityContext: SecurityContext = { securityLevel, hardeningEnabled: true, appliedCapabilities: [], droppedCapabilities: [] }; try { logger.info('Applying security hardening', { executionId, securityLevel }); // 1. Create and apply seccomp profile if (hardening.enableSeccomp) { const seccompProfile = await SeccompProfileManager.createSeccompProfile(securityLevel); const profilePath = `/tmp/mcp/seccomp-${executionId}.json`; await SeccompProfileManager.writeProfileToFile(seccompProfile, profilePath); securityContext.seccompProfileId = executionId; logger.debug('Seccomp profile applied', { executionId }); } // 2. Create user namespace for rootless execution if (hardening.enableUserNamespace) { const nsId = await UserNamespaceManager.createUserNamespace(UserNamespaceManager.getDefaultConfig()); await UserNamespaceManager.enforceNonRootExecution(); securityContext.userNamespaceId = nsId; logger.debug('User namespace created', { executionId, nsId }); } // 3. Apply mandatory access control if (hardening.enableMandatoryAccessControl) { await this.applyMandatoryAccessControl(executionId, hardening.macProfile); securityContext.macProfileId = `mac-${executionId}`; logger.debug('MAC profile applied', { executionId }); } // 4. Create resource limits if (hardening.enableResourceLimits) { const resourceLimits = hardening.customResourceLimits || ResourceController.getDefaultSecureLimits(); const resourceGroupId = await ResourceController.createResourceGroup(executionId, resourceLimits); securityContext.resourceGroupId = resourceGroupId; logger.debug('Resource limits applied', { executionId, resourceGroupId }); } // 5. Setup network isolation if (hardening.enableNetworkIsolation) { const networkPolicy = NetworkIsolation.getDefaultSecurePolicy(); if (hardening.networkPolicy === 'bridge') { networkPolicy.mode = 'bridge'; } else if (hardening.networkPolicy === 'custom') { networkPolicy.mode = 'custom'; } const networkNsId = await NetworkIsolation.createNetworkNamespace(networkPolicy); securityContext.networkNamespaceId = networkNsId; logger.debug('Network isolation applied', { executionId, networkNsId }); } // 6. Configure capability dropping if (hardening.enableCapabilityDropping) { const capConfig = await CapabilityManager.dropDangerousCapabilities(); if (hardening.allowedCapabilities) { capConfig.add = hardening.allowedCapabilities; } await CapabilityManager.validateCapabilityConfig(capConfig); securityContext.appliedCapabilities = capConfig.add; securityContext.droppedCapabilities = capConfig.drop; logger.debug('Capability configuration applied', { executionId, added: capConfig.add.length, dropped: capConfig.drop.length }); } logger.info('Security hardening applied successfully', { executionId, securityContext }); return securityContext; } catch (error) { logger.error('Failed to apply security hardening', { executionId, error }); // Cleanup any partially created resources await this.cleanupSecurityContext(executionId, securityContext); throw error; } } private async applyMandatoryAccessControl(executionId: string, macProfile?: 'apparmor' | 'selinux' | 'both'): Promise<void> { try { const defaultConfig = MandatoryAccessControl.getDefaultSecureConfig(); if (macProfile === 'apparmor' || macProfile === 'both' || !macProfile) { const apparmorProfile = await MandatoryAccessControl.createAppArmorProfile(defaultConfig.profileName); logger.debug('AppArmor profile created', { executionId, profileName: apparmorProfile.name }); } if (macProfile === 'selinux' || macProfile === 'both') { const selinuxProfile = await MandatoryAccessControl.createSELinuxPolicy(defaultConfig.profileName); logger.debug('SELinux policy created', { executionId, moduleName: selinuxProfile.name }); } } catch (error) { logger.warn('MAC profile setup failed, continuing without MAC', { executionId, error }); // Continue without MAC if it fails - some systems may not support it } } private getDefaultSecurityHardening(): SecurityHardeningConfig { return { enableSeccomp: true, enableUserNamespace: true, enableMandatoryAccessControl: true, enableResourceLimits: true, enableNetworkIsolation: true, enableCapabilityDropping: true, allowedCapabilities: [], // No capabilities by default networkPolicy: 'none', // Maximum isolation macProfile: 'apparmor' }; } /** * Enhanced security validation methods */ private async validateImageSecurity(image: string): Promise<void> { try { // Check if image is signed const signatureCheck = await this.checkImageSignature(image); if (!signatureCheck.valid) { logger.warn('Image signature validation failed', { image, reason: signatureCheck.reason }); } // Scan for known vulnerabilities await this.scanForVulnerabilities('validation', image); // Check image metadata for security issues await this.validateImageMetadata(image); } catch (error) { logger.error('Image security validation failed', { image, error }); throw error; } } private async checkImageSignature(image: string): Promise<{ valid: boolean; reason?: string }> { try { // This would integrate with image signing verification (e.g., Cosign, Notary) // For now, we'll implement basic checks // Check if image is from trusted registry const trustedRegistries = ['docker.io/library', 'gcr.io/distroless', 'quay.io/']; const isFromTrustedRegistry = trustedRegistries.some(registry => image.startsWith(registry)); if (!isFromTrustedRegistry) { return { valid: false, reason: 'Image not from trusted registry' }; } return { valid: true }; } catch (error) { return { valid: false, reason: `Signature check failed: ${error}` }; } } private async validateImageMetadata(image: string): Promise<void> { try { const { stdout } = await execAsync(`docker inspect ${image} --format='{{json .Config}}'`); const config = JSON.parse(stdout); // Check for dangerous configurations if (config.User === 'root' || config.User === '0') { logger.warn('Image runs as root user', { image }); } if (config.Privileged) { throw new Error('Image configured to run in privileged mode - rejected'); } // Check exposed ports if (config.ExposedPorts) { const exposedPorts = Object.keys(config.ExposedPorts); const privilegedPorts = exposedPorts.filter(port => { const portNum = parseInt(port.split('/')[0]); return portNum < 1024; }); if (privilegedPorts.length > 0) { logger.warn('Image exposes privileged ports', { image, ports: privilegedPorts }); } } } catch (error) { logger.warn('Failed to validate image metadata', { image, error }); // Continue execution - metadata validation is not critical } } private async validateCommandSecurity(commands: string[]): Promise<void> { for (const command of commands) { // Enhanced malicious pattern detection if (this.containsMaliciousPattern(command)) { throw new Error(`Command contains malicious patterns: ${command}`); } // Check for suspicious command combinations if (this.containsSuspiciousCombination(commands)) { throw new Error('Command sequence contains suspicious patterns'); } // Validate command length to prevent buffer overflow attempts if (command.length > 4096) { throw new Error('Command exceeds maximum length limit'); } } } private containsSuspiciousCombination(commands: string[]): boolean { const commandString = commands.join(' '); // Check for suspicious command chains const suspiciousPatterns = [ /wget.*&&.*chmod.*&&.*\.\//, // Download, make executable, run /curl.*\|.*sh/, // Pipe curl to shell /echo.*\|.*base64.*-d.*\|.*sh/, // Base64 decode and execute /python.*-c.*exec.*input/, // Python code injection /bash.*-c.*eval/, // Bash eval injection ]; return suspiciousPatterns.some(pattern => pattern.test(commandString)); } private async validateEnvironmentSecurity(env: Record<string, string>): Promise<void> { const dangerousEnvVars = [ 'LD_PRELOAD', 'LD_LIBRARY_PATH', 'PYTHONPATH', 'PATH', 'SHELL', 'IFS', 'PS1', 'PROMPT_COMMAND' ]; for (const [key, value] of Object.entries(env)) { // Check for dangerous environment variables if (dangerousEnvVars.includes(key)) { logger.warn('Potentially dangerous environment variable', { key, value }); } // Check for injection attempts in environment values if (this.containsMaliciousPattern(value)) { throw new Error(`Environment variable contains malicious content: ${key}`); } // Check for excessive environment variable size if (value.length > 1024) { throw new Error(`Environment variable value too large: ${key}`); } } } private isSecureEnvironmentVariable(key: string, value: string): boolean { // Whitelist approach for environment variables const allowedPrefixes = ['APP_', 'CUSTOM_', 'USER_', 'CONFIG_']; const allowedKeys = ['LANG', 'LC_ALL', 'TZ', 'HOME', 'TMPDIR']; const isAllowedKey = allowedKeys.includes(key); const hasAllowedPrefix = allowedPrefixes.some(prefix => key.startsWith(prefix)); return isAllowedKey || hasAllowedPrefix; } private filterSensitiveOutput(output: string): string { // Filter out potentially sensitive information from output const sensitivePatterns = [ /password[=:]\s*\S+/gi, /token[=:]\s*\S+/gi, /key[=:]\s*\S+/gi, /secret[=:]\s*\S+/gi, /api[_-]?key[=:]\s*\S+/gi, /access[_-]?token[=:]\s*\S+/gi, ]; let filteredOutput = output; sensitivePatterns.forEach(pattern => { filteredOutput = filteredOutput.replace(pattern, '[FILTERED]'); }); return filteredOutput; } /** * Enhanced monitoring and metrics collection */ private async performSecurityScanning(executionId: string, image: string, result: any): Promise<void> { try { await Promise.all([ this.scanForVulnerabilities(executionId, image), this.performDLPScan(result.stdout + result.stderr), this.scanForMalwareIndicators(result.stdout + result.stderr), this.checkForSecurityViolations(executionId) ]); } catch (error) { logger.error('Security scanning failed', { executionId, error }); // Continue execution but log the failure } } private async scanForMalwareIndicators(output: string): Promise<void> { const malwareIndicators = [ /xmrig/gi, // Cryptocurrency miner /cpuminer/gi, /stratum\+tcp/gi, // Mining pool connection /cryptonight/gi, /monero/gi, /bitcoin/gi, /ethereum/gi, /botnet/gi, /backdoor/gi, /rootkit/gi, ]; for (const pattern of malwareIndicators) { if (pattern.test(output)) { logger.error('Malware indicator detected in output', { pattern: pattern.source, output: output.substring(0, 200) }); } } } private async checkForSecurityViolations(executionId: string): Promise<void> { const securityContext = this.activeSecurityContexts.get(executionId); if (!securityContext) return; // Check for capability violations if available try { const violations = []; // This would check for actual security violations during execution // For now, we'll implement basic checks if (violations.length > 0) { logger.error('Security violations detected', { executionId, violations }); } } catch (error) { logger.warn('Security violation check failed', { executionId, error }); } } private async collectEnhancedResourceMetrics(containerName: string, securityContext: SecurityContext): Promise<any> { try { // Collect basic resource metrics const basicMetrics = await this.collectResourceMetrics(containerName); // Collect security-specific metrics const securityMetrics: any = { securityContext: { hardeningEnabled: securityContext.hardeningEnabled, securityLevel: securityContext.securityLevel, appliedCapabilities: securityContext.appliedCapabilities.length, droppedCapabilities: securityContext.droppedCapabilities.length, } }; // Collect resource controller metrics if available if (securityContext.resourceGroupId) { try { const resourceStats = await ResourceController.getResourceStats('default'); securityMetrics.resourceController = resourceStats; } catch (error) { logger.warn('Failed to collect resource controller metrics', { error }); } } // Collect network isolation metrics if available if (securityContext.networkNamespaceId) { try { const networkStats = await NetworkIsolation.getNetworkStats('default'); securityMetrics.networkIsolation = networkStats; } catch (error) { logger.warn('Failed to collect network isolation metrics', { error }); } } return { ...basicMetrics, ...securityMetrics }; } catch (error) { logger.error('Failed to collect enhanced resource metrics', { containerName, error }); return await this.collectResourceMetrics(containerName); // Fallback to basic metrics } } private async collectSecurityMetrics(executionId: string, securityContext: SecurityContext): Promise<any> { return { securityHardening: { enabled: securityContext.hardeningEnabled, level: securityContext.securityLevel, componentsEnabled: { seccomp: !!securityContext.seccompProfileId, userNamespace: !!securityContext.userNamespaceId, macProfile: !!securityContext.macProfileId, resourceLimits: !!securityContext.resourceGroupId, networkIsolation: !!securityContext.networkNamespaceId, capabilityDropping: securityContext.droppedCapabilities.length > 0 }, capabilities: { applied: securityContext.appliedCapabilities, dropped: securityContext.droppedCapabilities.length } } }; } private async getSecurityEvents(executionId: string): Promise<SecurityEvent[]> { try { // Collect security events from various sources const events: SecurityEvent[] = []; // This would collect events from seccomp, audit logs, etc. // For now, we'll return an empty array return events; } catch (error) { logger.error('Failed to collect security events', { executionId, error }); return []; } } /** * Gets statistics about security hardening */ public async getSecurityStats(): Promise<any> { return { activeExecutions: this.activeExecutions.size, activeSecurityContexts: this.activeSecurityContexts.size, securityManagers: { seccomp: true, userNamespace: true, mac: true, resourceController: true, networkIsolation: true, capabilityManager: true }, capabilities: await CapabilityManager.getCapabilityStats('default'), resources: await ResourceController.getResourceStats('default'), network: await NetworkIsolation.getNetworkStats('default') }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/perfecxion-ai/secure-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server