UNPKG

@pluggedin/pluggedin-mcp-proxy

Version:

Unified MCP proxy that aggregates all your MCP servers (STDIO, SSE, Streamable HTTP) into one powerful interface. Access any tool through a single connection, search across unified documents with built-in RAG, and receive notifications from any model. Tes

117 lines (116 loc) 3.42 kB
/** * Security utility functions for input validation and sanitization * Lightweight version for client-side MCP proxy */ /** * Validates a bearer token format * @param token - The token to validate * @returns true if valid, false otherwise */ export function validateBearerToken(token) { // Bearer tokens should be 32-256 characters of alphanumeric, hyphen, underscore, or dot return /^[a-zA-Z0-9\-_.]{32,256}$/.test(token); } /** * Validates environment variable name * @param name - The environment variable name to validate * @returns true if valid, false otherwise */ export function validateEnvVarName(name) { // Only allow alphanumeric characters and underscores return /^[A-Z0-9_]+$/i.test(name); } /** * Validates API base URL (permissive for client use) * @param url - The URL to validate * @returns true if valid, false otherwise */ export function validateApiUrl(url) { try { const parsed = new URL(url); return ['http:', 'https:'].includes(parsed.protocol); } catch { return false; } } /** * Lightweight rate limiter for client-side use */ export class RateLimiter { requests = []; windowMs; maxRequests; constructor(windowMs, maxRequests) { this.windowMs = windowMs; this.maxRequests = maxRequests; } checkLimit() { const now = Date.now(); const windowStart = now - this.windowMs; // Remove old requests this.requests = this.requests.filter(time => time > windowStart); // Check if we're over the limit if (this.requests.length >= this.maxRequests) { return false; } // Add current request this.requests.push(now); return true; } reset() { this.requests = []; } } /** * Sanitizes error messages to prevent information disclosure * @param error - The error object or message * @returns Sanitized error message */ export function sanitizeErrorMessage(error) { if (!error) return 'Unknown error occurred'; let message = error instanceof Error ? error.message : String(error); // Remove only the most sensitive patterns const sensitivePatterns = [ /password[=:]\s*[^\s]+/gi, /token[=:]\s*[^\s]+/gi, /Bearer\s+[^\s]+/gi, /api_key[=:]\s*[^\s]+/gi ]; for (const pattern of sensitivePatterns) { message = message.replace(pattern, '[REDACTED]'); } // Reasonable length limit if (message.length > 1000) { message = message.substring(0, 1000) + '...'; } return message; } /** * Basic request size validation * @param data - The data to validate * @param maxSize - Maximum allowed size in bytes * @returns true if valid, false otherwise */ export function validateRequestSize(data, maxSize = 10 * 1024 * 1024) { try { const size = JSON.stringify(data).length; return size <= maxSize; } catch { return false; } } /** * Lightweight timeout wrapper * @param operation - The async operation to wrap * @param timeoutMs - Timeout in milliseconds * @returns Promise that resolves with the operation result or rejects with timeout */ export function withTimeout(operation, timeoutMs) { return Promise.race([ operation, new Promise((_, reject) => setTimeout(() => reject(new Error('Operation timed out')), timeoutMs)) ]); }