Skip to main content
Glama
sanitize.ts5.16 kB
/** * Input sanitization utilities for HUMMBL MCP Server * Protects against injection attacks and validates user input */ /** * Maximum allowed length for problem descriptions */ const MAX_PROBLEM_LENGTH = 5000; /** * Maximum allowed length for search queries */ const MAX_QUERY_LENGTH = 500; /** * Patterns that indicate potential malicious content */ const SUSPICIOUS_PATTERNS = [ /<script[^>]*>.*?<\/script>/gi, // Script tags /javascript:/gi, // JavaScript protocol /on\w+\s*=/gi, // Event handlers /<iframe[^>]*>/gi, // Iframes /<object[^>]*>/gi, // Object tags /<embed[^>]*>/gi, // Embed tags ]; /** * Sanitize user-provided text input * Removes potentially dangerous content while preserving legitimate text */ export function sanitizeText(input: string, maxLength: number = MAX_PROBLEM_LENGTH): string { if (!input || typeof input !== "string") { return ""; } // Trim whitespace let sanitized = input.trim(); // Enforce length limits if (sanitized.length > maxLength) { sanitized = sanitized.substring(0, maxLength); } // Remove suspicious patterns for (const pattern of SUSPICIOUS_PATTERNS) { sanitized = sanitized.replace(pattern, ""); } // Remove null bytes and control characters (except newlines, tabs, carriage returns) // eslint-disable-next-line no-control-regex sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); return sanitized; } /** * Sanitize problem description with stricter validation */ export function sanitizeProblemDescription(problem: string): string { const sanitized = sanitizeText(problem, MAX_PROBLEM_LENGTH); // Problem descriptions must have minimum length if (sanitized.length < 10) { throw new Error("Problem description must be at least 10 characters"); } return sanitized; } /** * Sanitize search query with appropriate limits */ export function sanitizeQuery(query: string): string { const sanitized = sanitizeText(query, MAX_QUERY_LENGTH); // Queries must have minimum length if (sanitized.length < 2) { throw new Error("Query must be at least 2 characters"); } return sanitized; } /** * Validate model code format * Only allows codes matching the pattern [P|IN|CO|DE|RE|SY][1-20] */ export function validateModelCode(code: string): string { if (!code || typeof code !== "string") { throw new Error("Model code is required"); } const normalized = code.toUpperCase().trim(); const pattern = /^(P|IN|CO|DE|RE|SY)([1-9]|1[0-9]|20)$/; if (!pattern.test(normalized)) { throw new Error( `Invalid model code format: '${code}'. Expected format: [P|IN|CO|DE|RE|SY][1-20]` ); } return normalized; } /** * Validate transformation key * Only allows: P, IN, CO, DE, RE, SY */ export function validateTransformationKey(key: string): string { if (!key || typeof key !== "string") { throw new Error("Transformation key is required"); } const normalized = key.toUpperCase().trim(); const validKeys = ["P", "IN", "CO", "DE", "RE", "SY"]; if (!validKeys.includes(normalized)) { throw new Error(`Invalid transformation key: '${key}'. Valid keys: ${validKeys.join(", ")}`); } return normalized; } /** * Rate limiting helper - tracks request counts per time window */ class RateLimiter { private requests: Map<string, number[]> = new Map(); private windowMs: number; private maxRequests: number; constructor(windowMs: number = 60000, maxRequests: number = 100) { this.windowMs = windowMs; this.maxRequests = maxRequests; } /** * Check if request should be allowed */ public allow(identifier: string): boolean { const now = Date.now(); const timestamps = this.requests.get(identifier) || []; // Remove old timestamps outside the window const validTimestamps = timestamps.filter((ts) => now - ts < this.windowMs); if (validTimestamps.length >= this.maxRequests) { return false; } // Add current timestamp validTimestamps.push(now); this.requests.set(identifier, validTimestamps); return true; } /** * Get remaining requests in current window */ public remaining(identifier: string): number { const now = Date.now(); const timestamps = this.requests.get(identifier) || []; const validTimestamps = timestamps.filter((ts) => now - ts < this.windowMs); return Math.max(0, this.maxRequests - validTimestamps.length); } /** * Clear rate limit for identifier */ public clear(identifier: string): void { this.requests.delete(identifier); } /** * Clean up old entries (call periodically) */ public cleanup(): void { const now = Date.now(); for (const [identifier, timestamps] of this.requests.entries()) { const validTimestamps = timestamps.filter((ts) => now - ts < this.windowMs); if (validTimestamps.length === 0) { this.requests.delete(identifier); } else { this.requests.set(identifier, validTimestamps); } } } } /** * Global rate limiter instance * 100 requests per minute per tool */ export const rateLimiter = new RateLimiter(60000, 100);

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/hummbl-dev/mcp-server'

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