Skip to main content
Glama
security-service.ts12.2 kB
/** * Security Service Wrapper * * Provides a unified interface for all security operations. * Acts as a facade over individual security modules. * * Usage: * const security = new SecurityService(); * const result = await security.executeSecurely(plugin, params, llmClient); */ import { SanitisationHelper, type SanitisationResult } from './sanitisation.js'; import { PromptInjectionGuard, type InjectionDetectionResult } from './prompt-injection-guard.js'; import { OutputEncoder, type EncodingResult, type OutputContext } from './output-encoder.js'; export interface SecurityConfig { enableSanitisation?: boolean; enableInjectionDetection?: boolean; enableOutputEncoding?: boolean; injectionThreshold?: number; logSecurityEvents?: boolean; } export interface SecurityResult { safe: boolean; blocked: boolean; sanitised: any; warnings: string[]; riskLevel: 'low' | 'medium' | 'high' | 'critical'; processingTime: number; } export interface PluginInterface { name: string; category: 'analyze' | 'generate' | 'multifile' | 'custom' | 'system'; execute(params: any, llmClient: any): Promise<any>; getPrompt?(params: any): string; } export class SecurityService { private config: SecurityConfig; private startTime: number; constructor(config?: SecurityConfig) { this.config = { enableSanitisation: true, enableInjectionDetection: true, enableOutputEncoding: true, injectionThreshold: 0.5, logSecurityEvents: true, ...config }; this.startTime = Date.now(); } /** * Main wrapper method - executes a plugin with full security protection */ async executeSecurely( plugin: PluginInterface, params: any, llmClient: any ): Promise<any> { const startTime = Date.now(); const warnings: string[] = []; try { // Step 1: Sanitise and validate input parameters const secureParams = await this.secureParameters(params, plugin.name); // Check if any parameters were blocked if (secureParams.blocked) { throw new Error(`Security violation: ${secureParams.warnings.join(', ')}`); } warnings.push(...secureParams.warnings); // Step 2: Execute the plugin with secured parameters const result = await plugin.execute(secureParams.sanitised, llmClient); // Step 3: Secure the output const secureResult = await this.secureOutput(result, plugin.category); // Step 4: Add security metadata const processingTime = Date.now() - startTime; if (warnings.length > 0 && this.config.logSecurityEvents) { console.warn(`Security warnings for ${plugin.name}:`, warnings); } return { ...secureResult, __security: { safe: true, warnings, processingTime, timestamp: new Date().toISOString() } }; } catch (error) { // Sanitise error messages const sanitisedError = this.sanitiseError(error as Error, plugin.name); throw sanitisedError; } } /** * Secure input parameters */ async secureParameters(params: any, pluginName?: string): Promise<SecurityResult> { const startTime = Date.now(); const warnings: string[] = []; let blocked = false; let riskLevel: 'low' | 'medium' | 'high' | 'critical' = 'low'; if (!this.config.enableSanitisation && !this.config.enableInjectionDetection) { return { safe: true, blocked: false, sanitised: params, warnings: [], riskLevel: 'low', processingTime: Date.now() - startTime }; } const sanitised: any = {}; for (const [key, value] of Object.entries(params)) { if (typeof value === 'string') { // Determine context for this parameter const context = this.getParameterContext(key); // Step 1: Check for injection if enabled if (this.config.enableInjectionDetection) { const injectionResult = PromptInjectionGuard.analyseInjection( value, { source: 'parameter', pluginName } ); if (injectionResult.detected) { if (injectionResult.confidence >= (this.config.injectionThreshold || 0.5)) { if (injectionResult.riskLevel === 'critical') { blocked = true; warnings.push(`Critical injection attempt in parameter '${key}': ${injectionResult.mitigation}`); riskLevel = 'critical'; continue; } else { warnings.push(`Injection detected in parameter '${key}': ${injectionResult.mitigation}`); riskLevel = this.getHigherRiskLevel(riskLevel, injectionResult.riskLevel); } } } } // Step 2: Sanitise the parameter if enabled if (this.config.enableSanitisation) { const sanitisationResult = SanitisationHelper.sanitiseInput(value, context); if (sanitisationResult.blocked) { blocked = true; warnings.push(`Parameter '${key}' blocked: ${sanitisationResult.reason}`); continue; } sanitised[key] = sanitisationResult.cleaned; warnings.push(...sanitisationResult.warnings.map(w => `${key}: ${w}`)); } else { sanitised[key] = value; } } else { // Non-string values pass through (could add object sanitisation here) sanitised[key] = value; } } const processingTime = Date.now() - startTime; return { safe: !blocked && riskLevel !== 'critical', blocked, sanitised, warnings, riskLevel, processingTime }; } /** * Secure output data */ async secureOutput(output: any, pluginCategory?: string): Promise<any> { if (!this.config.enableOutputEncoding) { return output; } const outputContext = this.getOutputContext(pluginCategory); try { const safeResponse = OutputEncoder.createSafeResponse(output, outputContext); if (safeResponse.metadata.warnings.length > 0 && this.config.logSecurityEvents) { console.warn('Output encoding warnings:', safeResponse.metadata.warnings); } return safeResponse.data; } catch (error) { console.error('Error securing output:', error); return output; // Fallback to original output if encoding fails } } /** * Sanitise error messages to prevent information disclosure */ sanitiseError(error: Error, pluginName?: string): Error { if (!this.config.enableSanitisation) { return error; } let message = error.message || 'An error occurred'; // Remove sensitive information from error messages message = message .replace(/\/[a-zA-Z]:[\\\/][^\\\/\s]*[\\\/][^\\\/\s]*/g, '[PATH_REMOVED]') // Windows/Unix paths .replace(/Error: ENOENT: no such file or directory, open '([^']*)'/, 'Error: File not found') .replace(/at Object\.readFileSync[^)]*\)/g, 'at file read operation') .replace(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g, '[IP_REMOVED]') // IP addresses .replace(/\bfile:\/\/[^\s]*/g, '[FILE_URL_REMOVED]') // File URLs .replace(/API[_\s]?KEY[_\s]?[=:]\s*[^\s]+/gi, 'API_KEY=[REDACTED]'); // API keys const sanitisedError = new Error(message); sanitisedError.name = error.name || 'SecurityError'; if (this.config.logSecurityEvents) { console.warn(`Sanitised error for ${pluginName}: ${error.message} → ${message}`); } return sanitisedError; } /** * Quick security check for individual values */ async quickCheck( value: string, context: 'user-input' | 'file-content' | 'llm-response' | 'parameter' = 'parameter' ): Promise<SecurityResult> { const startTime = Date.now(); // Injection detection const injectionResult = PromptInjectionGuard.analyseInjection(value, { source: context }); // Input sanitisation const sanitisationResult = SanitisationHelper.sanitiseInput(value, 'general'); const blocked = sanitisationResult.blocked || (injectionResult.detected && injectionResult.riskLevel === 'critical'); return { safe: !injectionResult.detected && !sanitisationResult.blocked, blocked, sanitised: sanitisationResult.cleaned, warnings: [ ...sanitisationResult.warnings, ...(injectionResult.detected ? [injectionResult.mitigation] : []) ], riskLevel: injectionResult.detected ? injectionResult.riskLevel : 'low', processingTime: Date.now() - startTime }; } /** * Validate and sanitise file paths */ async validateFilePath(filePath: string): Promise<SecurityResult> { const startTime = Date.now(); const result = SanitisationHelper.sanitiseFilePath(filePath); return { safe: !result.blocked, blocked: result.blocked, sanitised: result.cleaned, warnings: result.warnings, riskLevel: result.blocked ? 'high' : 'low', processingTime: Date.now() - startTime }; } /** * Encode output for specific contexts */ encodeOutput(content: any, context: OutputContext): EncodingResult { return OutputEncoder.encode(content, { context }); } /** * Run comprehensive security tests */ runDiagnostics(): { sanitisation: boolean; injection: { passed: number; failed: number }; encoding: { passed: boolean; errors: string[] }; serviceHealth: boolean; } { try { const sanitisation = SanitisationHelper.validateSanitisation(); const injection = PromptInjectionGuard.runSecurityTests(); const encoding = OutputEncoder.validateEncoding(); return { sanitisation, injection, encoding, serviceHealth: true }; } catch (error) { console.error('Security diagnostics failed:', error); return { sanitisation: false, injection: { passed: 0, failed: 1 }, encoding: { passed: false, errors: ['Diagnostics failed'] }, serviceHealth: false }; } } /** * Get parameter context for security checks */ private getParameterContext(paramKey: string): 'file-path' | 'code' | 'prompt' | 'general' { const key = paramKey.toLowerCase(); if (key.includes('path') || key.includes('file') || key.includes('dir')) { return 'file-path'; } if (key === 'code' || key === 'content' || key.includes('source')) { return 'code'; } if (key === 'prompt' || key === 'instructions' || key.includes('instruction')) { return 'prompt'; } return 'general'; } /** * Get output context based on plugin category */ private getOutputContext(pluginCategory?: string): OutputContext { switch (pluginCategory) { case 'generate': return 'code'; case 'custom': return 'code'; // Custom prompts often generate code/HTML/CSS case 'analyze': case 'multifile': case 'system': return 'json'; default: return 'json'; } } /** * Compare risk levels and return the higher one */ private getHigherRiskLevel( current: 'low' | 'medium' | 'high' | 'critical', new_: 'low' | 'medium' | 'high' | 'critical' ): 'low' | 'medium' | 'high' | 'critical' { const levels = { low: 0, medium: 1, high: 2, critical: 3 }; return levels[new_] > levels[current] ? new_ : current; } /** * Update security configuration */ updateConfig(newConfig: Partial<SecurityConfig>): void { this.config = { ...this.config, ...newConfig }; } /** * Get current security configuration */ getConfig(): SecurityConfig { return { ...this.config }; } } // Singleton instance for global use export const securityService = new SecurityService(); // Export types for plugin developers export type { PluginInterface as SecurityPluginInterface, SecurityResult as SecurityServiceResult, SecurityConfig as SecurityServiceConfig };

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/houtini-ai/lm'

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