Skip to main content
Glama
security-optimized.js13.3 kB
import { ValidationError } from './errors.js'; // Security levels export const SECURITY_LEVELS = { STRICT: 'strict', MODERATE: 'moderate', BASIC: 'basic', DISABLED: 'disabled' }; // Granular security configuration with individual feature flags function getSecurityConfig() { return { LEVEL: process.env.SECURITY_LEVEL || SECURITY_LEVELS.MODERATE, // Content filtering options BLOCK_EXPLICIT_CONTENT: process.env.BLOCK_EXPLICIT_CONTENT !== 'false', BLOCK_VIOLENCE: process.env.BLOCK_VIOLENCE !== 'false', BLOCK_ILLEGAL_ACTIVITIES: process.env.BLOCK_ILLEGAL_ACTIVITIES !== 'false', BLOCK_ADULT_CONTENT: process.env.BLOCK_ADULT_CONTENT !== 'false', // Injection detection options DETECT_PROMPT_INJECTION: process.env.DETECT_PROMPT_INJECTION !== 'false', DETECT_SYSTEM_PROMPTS: process.env.DETECT_SYSTEM_PROMPTS !== 'false', DETECT_INSTRUCTION_OVERRIDE: process.env.DETECT_INSTRUCTION_OVERRIDE !== 'false', // Sanitization options SANITIZE_INPUT: process.env.SANITIZE_INPUT !== 'false', REMOVE_SCRIPTS: process.env.REMOVE_SCRIPTS !== 'false', LIMIT_REPEATED_CHARS: process.env.LIMIT_REPEATED_CHARS !== 'false', // Performance options ENABLE_PATTERN_CACHING: process.env.ENABLE_PATTERN_CACHING !== 'false', MAX_PROMPT_LENGTH_FOR_DEEP_SCAN: parseInt(process.env.MAX_PROMPT_LENGTH_FOR_DEEP_SCAN) || 1000, // Strictness overrides ALLOW_EDUCATIONAL_CONTENT: process.env.ALLOW_EDUCATIONAL_CONTENT === 'true', WHITELIST_PATTERNS: (process.env.WHITELIST_PATTERNS || '').split(',').filter(Boolean) }; } // Cached compiled patterns for better performance class PatternCache { constructor() { this.cache = new Map(); this.lastConfigHash = null; this.compiledPatterns = null; } getCompiledPatterns() { const config = getSecurityConfig(); const configHash = JSON.stringify(config); if (this.lastConfigHash === configHash && this.compiledPatterns) { return this.compiledPatterns; } this.compiledPatterns = this.compilePatterns(config); this.lastConfigHash = configHash; return this.compiledPatterns; } compilePatterns(config) { const patterns = { injection: [], suspicious: [], explicit: [], whitelist: [] }; // Base injection patterns - only include if detection is enabled if (config.DETECT_PROMPT_INJECTION) { if (config.DETECT_INSTRUCTION_OVERRIDE) { patterns.injection.push({ regex: /ignore\s+(previous|above|all|prior)\s.*?(instructions|commands|rules)/i, severity: 'high', type: 'instruction_override' }); } if (config.DETECT_SYSTEM_PROMPTS) { patterns.injection.push({ regex: /system\s*:\s*(you\s+are|act\s+as|pretend|forget|return)/i, severity: 'high', type: 'system_injection' }); } // Always include these critical patterns patterns.injection.push( { regex: /\{\{[^}]*\}\}|<\|[^|]*\|>|<system>|<\/system>/i, severity: 'high', type: 'template_injection' }, { regex: /\[INST\]|\[\/INST\]|<s>|<\/s>/i, severity: 'high', type: 'format_injection' }, { regex: /stop\.\s*new\s+(instruction|command)/i, severity: 'high', type: 'instruction_break' } ); } // Suspicious patterns - lighter weight detection if (config.LEVEL !== SECURITY_LEVELS.DISABLED) { patterns.suspicious.push( { regex: /what\s+(are\s+your|is\s+the)\s+(instructions|system\s+prompt)/i, severity: 'medium', type: 'prompt_probing' }, { regex: /(admin|root|developer)\s+(access|mode|privileges)/i, severity: 'medium', type: 'privilege_escalation' } ); } // Content filtering patterns - modular based on config if (config.BLOCK_EXPLICIT_CONTENT) { if (config.BLOCK_VIOLENCE) { patterns.explicit.push({ regex: /how\s+to\s+(kill|murder|harm)\s+/i, severity: 'high', type: 'violence' }); } if (config.BLOCK_ILLEGAL_ACTIVITIES) { patterns.explicit.push({ regex: /how\s+to\s+(hack|steal|fraud)/i, severity: 'high', type: 'illegal_activity' }); } } // Whitelist patterns for educational content if (config.ALLOW_EDUCATIONAL_CONTENT) { patterns.whitelist.push( { regex: /(educational|academic|research|study|learn about)/i, type: 'educational' }, { regex: /(information about|explain|describe|what is)/i, type: 'informational' } ); } // Custom whitelist patterns config.WHITELIST_PATTERNS.forEach(pattern => { try { patterns.whitelist.push({ regex: new RegExp(pattern, 'i'), type: 'custom_whitelist' }); } catch (e) { console.warn('Invalid whitelist pattern:', pattern); } }); return patterns; } } const patternCache = new PatternCache(); /** * Optimized input sanitization with configurable options */ export function sanitizeInput(input) { const config = getSecurityConfig(); if (!config.SANITIZE_INPUT || config.LEVEL === SECURITY_LEVELS.DISABLED) { return input; } let sanitized = input; // Remove control characters (always safe) sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ''); // Optional script removal if (config.REMOVE_SCRIPTS) { sanitized = sanitized.replace(/<script[^>]*>.*?<\/script>/gi, ''); sanitized = sanitized.replace(/javascript\s*:/gi, ''); } // Optional repeated character limiting if (config.LIMIT_REPEATED_CHARS) { sanitized = sanitized.replace(/(.)\1{100,}/g, '$1'.repeat(10)); } // Limit excessive whitespace sanitized = sanitized.replace(/\s{10,}/g, ' '.repeat(5)); // Handle escape sequences properly sanitized = sanitized.replace(/\\n/g, '\n').replace(/\\t/g, '\t'); return sanitized.trim(); } /** * Fast pattern matching with early termination */ function fastPatternMatch(text, patterns, maxMatches = 3) { const matches = []; const lowerText = text.toLowerCase(); for (const pattern of patterns) { if (pattern.regex.test(text)) { matches.push(pattern); if (matches.length >= maxMatches) { break; // Early termination for performance } } } return matches; } /** * Optimized security validation with performance considerations */ export function validatePromptSecurity(prompt) { const config = getSecurityConfig(); const results = { original: prompt, sanitized: null, isValid: true, warnings: [], blocked: false, reasons: [], performance: { startTime: Date.now(), checks: [] } }; // Quick exit for disabled security if (config.LEVEL === SECURITY_LEVELS.DISABLED) { results.sanitized = prompt; results.performance.totalTime = Date.now() - results.performance.startTime; return results; } try { const checkStart = Date.now(); // Step 1: Sanitize input results.sanitized = sanitizeInput(prompt); results.performance.checks.push({ name: 'sanitization', time: Date.now() - checkStart }); // Step 2: Check prompt length for deep scanning const isLongPrompt = results.sanitized.length > config.MAX_PROMPT_LENGTH_FOR_DEEP_SCAN; const patterns = patternCache.getCompiledPatterns(); // Step 3: Whitelist check first (performance optimization) if (patterns.whitelist.length > 0) { const whitelistStart = Date.now(); const whitelistMatches = fastPatternMatch(results.sanitized, patterns.whitelist, 1); results.performance.checks.push({ name: 'whitelist', time: Date.now() - whitelistStart }); if (whitelistMatches.length > 0) { // Allow whitelisted content with reduced scrutiny results.warnings.push({ type: 'whitelisted_content', description: 'Content allowed due to whitelist match', match: whitelistMatches[0].type }); results.performance.totalTime = Date.now() - results.performance.startTime; return results; } } // Step 4: Fast injection detection if (config.DETECT_PROMPT_INJECTION && patterns.injection.length > 0) { const injectionStart = Date.now(); const injectionMatches = fastPatternMatch( results.sanitized, patterns.injection, isLongPrompt ? 1 : 3 // Reduce checks for long prompts ); results.performance.checks.push({ name: 'injection', time: Date.now() - injectionStart }); const highSeverityInjections = injectionMatches.filter(m => m.severity === 'high'); if (highSeverityInjections.length > 0) { results.isValid = false; results.blocked = true; results.reasons.push({ type: 'prompt_injection', description: 'Potential prompt injection detected', patterns: highSeverityInjections }); results.performance.totalTime = Date.now() - results.performance.startTime; return results; } // Store injection matches for later aggregation if (injectionMatches.length > 0) { results.warnings.push({ type: 'potential_injection', description: 'Potential injection patterns detected', patterns: injectionMatches }); } } // Step 5: Content filtering (skip for very long prompts in basic mode) if (config.BLOCK_EXPLICIT_CONTENT && patterns.explicit.length > 0 && !(isLongPrompt && config.LEVEL === SECURITY_LEVELS.BASIC)) { const contentStart = Date.now(); const contentMatches = fastPatternMatch(results.sanitized, patterns.explicit, 2); results.performance.checks.push({ name: 'content_filter', time: Date.now() - contentStart }); if (contentMatches.length > 0) { results.isValid = false; results.blocked = true; results.reasons.push({ type: 'explicit_content', description: 'Explicit or harmful content detected', patterns: contentMatches }); results.performance.totalTime = Date.now() - results.performance.startTime; return results; } } // Step 6: Suspicious pattern detection (warnings only) if (patterns.suspicious.length > 0 && !isLongPrompt) { const suspiciousStart = Date.now(); const suspiciousMatches = fastPatternMatch(results.sanitized, patterns.suspicious, 1); results.performance.checks.push({ name: 'suspicious', time: Date.now() - suspiciousStart }); if (suspiciousMatches.length > 0) { results.warnings.push({ type: 'suspicious_patterns', description: 'Suspicious patterns detected but not blocked', patterns: suspiciousMatches }); } } results.performance.totalTime = Date.now() - results.performance.startTime; return results; } catch (error) { results.isValid = false; results.blocked = true; results.reasons.push({ type: 'validation_error', description: 'Error during security validation', error: error.message }); results.performance.totalTime = Date.now() - results.performance.startTime; return results; } } /** * Simplified security check for production use */ export function securityCheck(prompt) { // Graceful handling of null/undefined if (prompt == null) { throw new ValidationError('Invalid prompt: cannot be null or undefined'); } if (typeof prompt !== 'string') { throw new ValidationError('Invalid prompt: must be a string'); } const validation = validatePromptSecurity(prompt); // Log performance metrics in debug mode if (process.env.NODE_ENV !== 'test' && process.env.LOG_LEVEL === 'debug') { console.debug('Security validation performance:', validation.performance); } if (validation.blocked) { const reasons = validation.reasons.map(r => r.description).join('; '); throw new ValidationError(`Security check failed: ${reasons}`); } // Log warnings in non-test environments if (validation.warnings.length > 0 && process.env.NODE_ENV !== 'test') { console.warn('Security warnings for prompt:', validation.warnings); } return validation.sanitized; } /** * Performance monitoring utilities */ export function getSecurityStats() { return { cacheStats: { hasCompiledPatterns: !!patternCache.compiledPatterns, lastConfigUpdate: patternCache.lastConfigHash ? 'cached' : 'not_cached' }, config: getSecurityConfig() }; } /** * Reset pattern cache (useful for testing or config changes) */ export function resetPatternCache() { patternCache.cache.clear(); patternCache.lastConfigHash = null; patternCache.compiledPatterns = null; } // Export for backward compatibility export const SECURITY_CONFIG = getSecurityConfig(); export { detectPromptInjection, filterExplicitContent } from './security.js';

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/fakoli/mcp-ai-bridge'

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