Skip to main content
Glama
security.ts5.87 kB
import crypto from 'crypto'; import { promisify } from 'util'; import { exec as execCallback } from 'child_process'; const execAsync = promisify(execCallback); /** * Security utilities for the MCP server * Following WCGW principles - assume everything is malicious */ export class SecurityManager { private static readonly ALLOWED_PATH_REGEX = /^[a-zA-Z0-9\-_.\/]+$/; private static readonly MAX_PATH_LENGTH = 1024; private static readonly COMMAND_TIMEOUT = 30000; // 30 seconds /** * Sanitize file paths to prevent directory traversal */ static sanitizePath(inputPath: string): string { if (!inputPath || typeof inputPath !== 'string') { throw new Error('Invalid path input'); } // Remove any null bytes let cleanPath = inputPath.replace(/\0/g, ''); // Normalize the path cleanPath = cleanPath.replace(/\\/g, '/'); // Remove any .. sequences cleanPath = cleanPath.replace(/\.\./g, ''); // Ensure path doesn't start with / if (cleanPath.startsWith('/') && !cleanPath.startsWith('/Users')) { throw new Error('Absolute paths outside user directory not allowed'); } // Check length if (cleanPath.length > this.MAX_PATH_LENGTH) { throw new Error('Path too long'); } return cleanPath; } /** * Sanitize command inputs to prevent injection */ static sanitizeCommand(command: string): string { if (!command || typeof command !== 'string') { throw new Error('Invalid command input'); } // Remove dangerous characters const dangerous = /[;&|`$(){}[\]<>]/g; return command.replace(dangerous, ''); } /** * Execute command with timeout and resource limits */ static async executeSecureCommand( command: string, options: { timeout?: number; maxBuffer?: number; cwd?: string; } = {} ): Promise<{ stdout: string; stderr: string }> { const timeout = options.timeout || this.COMMAND_TIMEOUT; const maxBuffer = options.maxBuffer || 10 * 1024 * 1024; // 10MB // Create abort controller for timeout const ac = new AbortController(); const timeoutId = setTimeout(() => ac.abort(), timeout); try { const result = await execAsync(command, { signal: ac.signal, maxBuffer, cwd: options.cwd, env: { ...process.env, // Restrict environment PATH: '/usr/local/bin:/usr/bin:/bin', NODE_ENV: 'production' } }); clearTimeout(timeoutId); return result; } catch (error: any) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error(`Command timed out after ${timeout}ms`); } throw new Error(`Command execution failed: ${error.message}`); } } /** * Validate input against schema */ static validateInput(input: any, schema: any): void { // This would integrate with a proper validation library // For now, basic type checking if (!input || typeof input !== 'object') { throw new Error('Invalid input format'); } } /** * Generate secure random tokens */ static generateSecureToken(length: number = 32): string { return crypto.randomBytes(length).toString('hex'); } /** * Hash sensitive data */ static async hashData(data: string): Promise<string> { return crypto.createHash('sha256').update(data).digest('hex'); } /** * Encrypt sensitive configuration */ static encrypt(text: string, key: string): string { const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv( 'aes-256-cbc', Buffer.from(key, 'hex'), iv ); let encrypted = cipher.update(text); encrypted = Buffer.concat([encrypted, cipher.final()]); return iv.toString('hex') + ':' + encrypted.toString('hex'); } /** * Decrypt sensitive configuration */ static decrypt(text: string, key: string): string { const textParts = text.split(':'); const iv = Buffer.from(textParts.shift()!, 'hex'); const encryptedText = Buffer.from(textParts.join(':'), 'hex'); const decipher = crypto.createDecipheriv( 'aes-256-cbc', Buffer.from(key, 'hex'), iv ); let decrypted = decipher.update(encryptedText); decrypted = Buffer.concat([decrypted, decipher.final()]); return decrypted.toString(); } } // Rate limiting implementation export class RateLimiter { private requests: Map<string, number[]> = new Map(); private readonly windowMs: number; private readonly maxRequests: number; constructor(windowMs: number = 60000, maxRequests: number = 100) { this.windowMs = windowMs; this.maxRequests = maxRequests; // Clean up old entries periodically setInterval(() => this.cleanup(), windowMs); } /** * Check if request should be allowed */ isAllowed(identifier: string): boolean { const now = Date.now(); const requests = this.requests.get(identifier) || []; // Filter out old requests const recentRequests = requests.filter( time => now - time < this.windowMs ); if (recentRequests.length >= this.maxRequests) { return false; } recentRequests.push(now); this.requests.set(identifier, recentRequests); return true; } /** * Clean up old entries */ private cleanup(): void { const now = Date.now(); for (const [identifier, requests] of this.requests.entries()) { const recentRequests = requests.filter( time => now - time < this.windowMs ); if (recentRequests.length === 0) { this.requests.delete(identifier); } else { this.requests.set(identifier, recentRequests); } } } }

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/Rajawatrajat/mcp-software-engineer'

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