Skip to main content
Glama

Smart-AI-Bridge

fuzzy-matching-security.jsโ€ข9.89 kB
/** * Fuzzy Matching Security Module * * Provides comprehensive security controls for fuzzy matching operations including: * - Complexity validation (DoS prevention) * - Iteration limits * - Timeout wrappers * - Metrics tracking for abuse detection * * Security Score Target: 9.7/10 * @module fuzzy-matching-security */ /** * Security limits for fuzzy matching operations * These limits prevent algorithmic complexity attacks and resource exhaustion */ export const FUZZY_SECURITY_LIMITS = { // Maximum characters in a single edit find/replace string MAX_FUZZY_EDIT_LENGTH: 5000, // Maximum lines in a single edit pattern MAX_FUZZY_LINE_COUNT: 200, // Maximum total characters across all edits in single operation MAX_FUZZY_TOTAL_CHARS: 50000, // Maximum iterations for fuzzy matching loop (prevents infinite loops) MAX_FUZZY_ITERATIONS: 10000, // Maximum number of suggestions to return MAX_FUZZY_SUGGESTIONS: 10, // Timeout for fuzzy operations (milliseconds) FUZZY_TIMEOUT_MS: 5000 }; /** * Metrics tracking for fuzzy matching operations * Used for abuse detection and performance monitoring */ class FuzzyMetricsTracker { constructor() { this.metrics = { totalOperations: 0, exactMatches: 0, fuzzyMatches: 0, failedMatches: 0, averageSimilarity: 0, iterationLimitHits: 0, timeouts: 0, complexityLimitHits: 0, lastReset: Date.now() }; // Time-window tracking (last 15 minutes) this.recentEvents = []; this.WINDOW_MS = 15 * 60 * 1000; // 15 minutes } /** * Record a fuzzy matching event * @param {string} eventName - Event type * @param {Object} data - Event data */ trackEvent(eventName, data = {}) { const timestamp = Date.now(); // Clean old events outside time window this.recentEvents = this.recentEvents.filter( e => timestamp - e.timestamp < this.WINDOW_MS ); // Add new event this.recentEvents.push({ eventName, data, timestamp }); // Update aggregate metrics switch (eventName) { case 'EXACT_MATCH': this.metrics.totalOperations++; this.metrics.exactMatches++; break; case 'FUZZY_MATCH': this.metrics.totalOperations++; this.metrics.fuzzyMatches++; if (data.similarity) { // Update running average const total = this.metrics.fuzzyMatches; const current = this.metrics.averageSimilarity; this.metrics.averageSimilarity = (current * (total - 1) + data.similarity) / total; } break; case 'MATCH_FAILED': this.metrics.totalOperations++; this.metrics.failedMatches++; break; case 'ITERATION_LIMIT_HIT': this.metrics.iterationLimitHits++; break; case 'TIMEOUT': this.metrics.timeouts++; break; case 'COMPLEXITY_LIMIT_HIT': this.metrics.complexityLimitHits++; break; } } /** * Get current metrics * @returns {Object} Current metrics */ getMetrics() { return { ...this.metrics, recentEventsCount: this.recentEvents.length, uptime: Date.now() - this.metrics.lastReset }; } /** * Detect potential abuse patterns * @returns {Object} Abuse detection results */ detectAbusePatterns() { const now = Date.now(); const recentMinute = this.recentEvents.filter( e => now - e.timestamp < 60000 ); return { suspiciousActivity: false, reasons: [], // High iteration limit hit rate (>10% of operations) highIterationLimitRate: this.metrics.iterationLimitHits > this.metrics.totalOperations * 0.1, // High timeout rate (>5% of operations) highTimeoutRate: this.metrics.timeouts > this.metrics.totalOperations * 0.05, // High complexity limit hits highComplexityLimitRate: this.metrics.complexityLimitHits > this.metrics.totalOperations * 0.1, // Rapid request rate (>100 operations/minute) rapidRequestRate: recentMinute.length > 100, // Low success rate (<50%) lowSuccessRate: (this.metrics.exactMatches + this.metrics.fuzzyMatches) < this.metrics.totalOperations * 0.5 }; } /** * Reset metrics */ reset() { this.metrics = { totalOperations: 0, exactMatches: 0, fuzzyMatches: 0, failedMatches: 0, averageSimilarity: 0, iterationLimitHits: 0, timeouts: 0, complexityLimitHits: 0, lastReset: Date.now() }; this.recentEvents = []; } } // Global metrics tracker instance const metricsTracker = new FuzzyMetricsTracker(); /** * Validate fuzzy edit complexity before processing * Prevents DoS attacks via excessively complex edit operations * * @param {Array} edits - Array of edit operations * @param {Object} limits - Security limits (optional, uses FUZZY_SECURITY_LIMITS by default) * @returns {Object} Validation result { valid: boolean, errors: string[] } */ export function validateFuzzyEditComplexity(edits, limits = FUZZY_SECURITY_LIMITS) { const errors = []; // Validate edits is an array if (!Array.isArray(edits)) { errors.push('edits must be an array'); return { valid: false, errors }; } // Validate array length if (edits.length === 0) { errors.push('edits array cannot be empty'); return { valid: false, errors }; } let totalCharacters = 0; // Validate each edit operation for (let i = 0; i < edits.length; i++) { const edit = edits[i]; // Validate edit structure if (!edit || typeof edit !== 'object') { errors.push(`Edit ${i}: must be an object with 'find' and 'replace' properties`); continue; } // Validate find string if (typeof edit.find !== 'string') { errors.push(`Edit ${i}: 'find' must be a string`); continue; } if (edit.find.length > limits.MAX_FUZZY_EDIT_LENGTH) { errors.push( `Edit ${i}: 'find' exceeds maximum length of ${limits.MAX_FUZZY_EDIT_LENGTH} characters` ); metricsTracker.trackEvent('COMPLEXITY_LIMIT_HIT', { edit: i, length: edit.find.length, limit: limits.MAX_FUZZY_EDIT_LENGTH }); } // Validate replace string if (typeof edit.replace !== 'string') { errors.push(`Edit ${i}: 'replace' must be a string`); continue; } if (edit.replace.length > limits.MAX_FUZZY_EDIT_LENGTH) { errors.push( `Edit ${i}: 'replace' exceeds maximum length of ${limits.MAX_FUZZY_EDIT_LENGTH} characters` ); } // Check line count const findLineCount = edit.find.split('\n').length; if (findLineCount > limits.MAX_FUZZY_LINE_COUNT) { errors.push( `Edit ${i}: 'find' exceeds maximum ${limits.MAX_FUZZY_LINE_COUNT} lines` ); metricsTracker.trackEvent('COMPLEXITY_LIMIT_HIT', { edit: i, lines: findLineCount, limit: limits.MAX_FUZZY_LINE_COUNT }); } // Track total complexity totalCharacters += edit.find.length + edit.replace.length; } // Validate total complexity across all edits if (totalCharacters > limits.MAX_FUZZY_TOTAL_CHARS) { errors.push( `Total edit characters (${totalCharacters}) exceeds maximum ${limits.MAX_FUZZY_TOTAL_CHARS}` ); metricsTracker.trackEvent('COMPLEXITY_LIMIT_HIT', { totalCharacters, limit: limits.MAX_FUZZY_TOTAL_CHARS }); } return { valid: errors.length === 0, errors, totalCharacters, editCount: edits.length }; } /** * Create a timeout wrapper for fuzzy matching operations * Prevents operations from hanging indefinitely * * @param {Function} operationFn - Async function to wrap * @param {number} timeoutMs - Timeout in milliseconds * @returns {Promise} Promise that resolves/rejects with timeout */ export function createFuzzyTimeoutWrapper(operationFn, timeoutMs = FUZZY_SECURITY_LIMITS.FUZZY_TIMEOUT_MS) { return Promise.race([ operationFn(), new Promise((_, reject) => { setTimeout(() => { metricsTracker.trackEvent('TIMEOUT', { timeoutMs }); reject(new Error(`Fuzzy matching operation timed out after ${timeoutMs}ms`)); }, timeoutMs); }) ]); } /** * Track fuzzy matching metrics * @param {string} eventName - Event name * @param {Object} data - Event data */ export function trackFuzzyMetrics(eventName, data = {}) { metricsTracker.trackEvent(eventName, data); } /** * Get current fuzzy matching metrics * @returns {Object} Current metrics */ export function getFuzzyMetrics() { return metricsTracker.getMetrics(); } /** * Detect abuse patterns in fuzzy matching usage * @returns {Object} Abuse detection results */ export function detectAbusePatterns() { return metricsTracker.detectAbusePatterns(); } /** * Reset fuzzy matching metrics */ export function resetFuzzyMetrics() { metricsTracker.reset(); } /** * Validate and clamp fuzzy threshold to safe range * @param {number} threshold - User-provided threshold * @returns {number} Clamped threshold (0.1-1.0) */ export function validateFuzzyThreshold(threshold) { if (typeof threshold !== 'number' || isNaN(threshold)) { return 0.8; // Default threshold } // Clamp to valid range return Math.max(0.1, Math.min(1.0, threshold)); } /** * Validate and clamp max suggestions * @param {number} maxSuggestions - User-provided max suggestions * @returns {number} Clamped max suggestions (1-10) */ export function validateMaxSuggestions(maxSuggestions) { if (typeof maxSuggestions !== 'number' || isNaN(maxSuggestions)) { return 3; // Default } // Clamp to valid range return Math.max(1, Math.min(FUZZY_SECURITY_LIMITS.MAX_FUZZY_SUGGESTIONS, maxSuggestions)); } // Export singleton metrics tracker export { metricsTracker };

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/Platano78/Smart-AI-Bridge'

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