Skip to main content
Glama
AutomatedRemediation.ts20 kB
/** * Automated Security Remediation System * Provides intelligent automated fixes for detected vulnerabilities */ import * as fs from "fs/promises"; import * as path from "path"; import { SecurityVulnerability, SecurityScanResult } from "./AISecurityScanner.js"; import { SecurityUtils } from "./SecurityConfig.js"; import { SecurityValidationError } from "./InputValidator.js"; import { LoggerFactory } from "@/utils/logger.js"; interface RemediationAction { id: string; type: "replace" | "insert" | "delete" | "config" | "file"; target: { file?: string | undefined; line?: number | undefined; pattern?: RegExp | undefined; value?: string | undefined; }; replacement: { content?: string; config?: Record<string, unknown>; action?: string; }; backup: { enabled: boolean; path?: string; }; validation: { test?: string; expected?: unknown; }; } export interface RemediationResult { vulnerabilityId: string; success: boolean; action: string; details: string; timestamp: Date; backupPath?: string | undefined; validationResult?: boolean | undefined; } interface RemediationPlan { planId: string; vulnerabilities: SecurityVulnerability[]; actions: RemediationAction[]; estimatedDuration: number; riskLevel: "low" | "medium" | "high"; requiresApproval: boolean; } /** * Automated remediation patterns for common vulnerabilities */ const REMEDIATION_PATTERNS = { sqlInjection: { pattern: /(SELECT|INSERT|UPDATE|DELETE).*?[\'\"].*?[\'\"].*?(WHERE|FROM|INTO)/gi, replacement: "// TODO: Replace with parameterized query", confidence: 0.7, }, xssSimple: { pattern: /innerHTML\s*=\s*[^;]+;?/gi, replacement: "textContent = $1; // XSS remediation", confidence: 0.8, }, pathTraversal: { pattern: /\.\.[\/\\]/g, replacement: "", confidence: 0.9, }, credentialExposure: { pattern: /(password|secret|key|token)\s*[:=]\s*['"][^'"]+['"]/gi, replacement: "$1 = process.env.$1?.toUpperCase() || '[REQUIRED]'", confidence: 0.85, }, httpToHttps: { pattern: /http:\/\//gi, replacement: "https://", confidence: 0.95, }, insecureConfig: { pattern: /(ssl|secure|verify)\s*[:=]\s*false/gi, replacement: "$1: true", confidence: 0.9, }, }; /** * Automated Security Remediation Engine */ export class AutomatedRemediation { private readonly logger = LoggerFactory.security(); private remediationHistory: RemediationResult[] = []; private backupDirectory = "security-backups"; /** * Create remediation plan for scan results */ async createRemediationPlan(scanResult: SecurityScanResult): Promise<RemediationPlan> { const planId = SecurityUtils.generateSecureToken(16); const remediableVulns = scanResult.vulnerabilities.filter((v) => v.remediation.automated); this.logger.info("Creating remediation plan", { planId, remediableVulnerabilities: remediableVulns.length, totalVulnerabilities: scanResult.vulnerabilities.length, }); const actions: RemediationAction[] = []; let estimatedDuration = 0; let maxRiskLevel: "low" | "medium" | "high" = "low"; for (const vulnerability of remediableVulns) { const action = await this.createRemediationAction(vulnerability); if (action) { actions.push(action); estimatedDuration += 30; // 30 seconds per action estimate // Update risk level if (vulnerability.severity === "critical" || vulnerability.severity === "high") { maxRiskLevel = "high"; } else if (vulnerability.severity === "medium" && maxRiskLevel === "low") { maxRiskLevel = "medium"; } } } const requiresApproval = maxRiskLevel === "high" || actions.length > 10; return { planId, vulnerabilities: remediableVulns, actions, estimatedDuration, riskLevel: maxRiskLevel, requiresApproval, }; } /** * Create remediation action for a specific vulnerability */ private async createRemediationAction(vulnerability: SecurityVulnerability): Promise<RemediationAction | null> { const actionId = SecurityUtils.generateSecureToken(12); switch (vulnerability.type) { case "SQL Injection": return this.createSQLInjectionRemediation(actionId, vulnerability); case "Cross-Site Scripting (XSS)": return this.createXSSRemediation(actionId, vulnerability); case "Path Traversal": return this.createPathTraversalRemediation(actionId, vulnerability); case "Credential Exposure": return this.createCredentialExposureRemediation(actionId, vulnerability); case "Insecure Configuration": return this.createConfigRemediation(actionId, vulnerability); case "Information Disclosure": return this.createInfoDisclosureRemediation(actionId, vulnerability); default: return null; } } /** * Create SQL injection remediation */ private createSQLInjectionRemediation(actionId: string, vulnerability: SecurityVulnerability): RemediationAction { return { id: actionId, type: "replace", target: { file: vulnerability.location.file, line: vulnerability.location.line, pattern: REMEDIATION_PATTERNS.sqlInjection.pattern, }, replacement: { content: "// SECURITY FIX: Replace with parameterized query to prevent SQL injection\n" + "// Example: db.query('SELECT * FROM users WHERE id = ?', [userId])", }, backup: { enabled: true, }, validation: { test: 'grep -n "SELECT.*WHERE" $file | wc -l', expected: 0, }, }; } /** * Create XSS remediation */ private createXSSRemediation(actionId: string, vulnerability: SecurityVulnerability): RemediationAction { return { id: actionId, type: "replace", target: { file: vulnerability.location.file, line: vulnerability.location.line, pattern: /innerHTML\s*=\s*([^;]+);?/gi, }, replacement: { content: "textContent = $1; // XSS remediation: use textContent instead of innerHTML", }, backup: { enabled: true, }, validation: { test: 'grep -n "innerHTML" $file | wc -l', expected: 0, }, }; } /** * Create path traversal remediation */ private createPathTraversalRemediation(actionId: string, vulnerability: SecurityVulnerability): RemediationAction { return { id: actionId, type: "replace", target: { file: vulnerability.location.file, line: vulnerability.location.line, pattern: /\.\.[\/\\]/g, }, replacement: { content: "", // Remove path traversal sequences }, backup: { enabled: true, }, validation: { test: 'grep -n "\\.\\./" $file | wc -l', expected: 0, }, }; } /** * Create credential exposure remediation */ private createCredentialExposureRemediation( actionId: string, vulnerability: SecurityVulnerability, ): RemediationAction { return { id: actionId, type: "replace", target: { file: vulnerability.location.file, line: vulnerability.location.line, pattern: /(password|secret|key|token)\s*[:=]\s*['"][^'"]+['"]/gi, }, replacement: { content: "// SECURITY FIX: Credential moved to environment variable\n" + "// Add to .env: $1=your_actual_value\n" + "$1: process.env.$1 || (() => { throw new Error('$1 environment variable required'); })()", }, backup: { enabled: true, }, validation: { test: 'grep -n "password.*=.*[\'\\"]" $file | wc -l', expected: 0, }, }; } /** * Create configuration remediation */ private createConfigRemediation(actionId: string, vulnerability: SecurityVulnerability): RemediationAction { return { id: actionId, type: "replace", target: { file: vulnerability.location.file, line: vulnerability.location.line, pattern: /(ssl|secure|verify)\s*[:=]\s*false/gi, }, replacement: { content: "$1: true // Security fix: enabled secure configuration", }, backup: { enabled: true, }, validation: { test: 'grep -n "ssl.*false" $file | wc -l', expected: 0, }, }; } /** * Create information disclosure remediation */ private createInfoDisclosureRemediation(actionId: string, vulnerability: SecurityVulnerability): RemediationAction { return { id: actionId, type: "replace", target: { file: vulnerability.location.file, line: vulnerability.location.line, pattern: /(debug|trace|error)\s*[:=]\s*true/gi, }, replacement: { content: "$1: process.env.NODE_ENV !== 'production' // Security fix: disable in production", }, backup: { enabled: true, }, validation: { test: 'grep -n "debug.*true" $file | wc -l', expected: 0, }, }; } /** * Execute remediation plan */ async executeRemediationPlan( plan: RemediationPlan, options: { dryRun?: boolean; requireConfirmation?: boolean; } = {}, ): Promise<RemediationResult[]> { this.logger.info("Executing remediation plan", { planId: plan.planId, actionCount: plan.actions.length, dryRun: options.dryRun, }); if (options.dryRun) { this.logger.info("Running in dry-run mode - no changes will be made"); return this.simulateRemediationActions(plan.actions); } const results: RemediationResult[] = []; // Ensure backup directory exists await this.ensureBackupDirectory(); for (const action of plan.actions) { try { this.logger.debug("Executing remediation action", { actionId: action.id, actionType: action.type, targetFile: action.target.file, }); const result = await this.executeRemediationAction(action); results.push(result); if (!result.success) { this.logger.error("Remediation action failed", { actionId: action.id, details: result.details, }); } } catch (_error) { this.logger.error("Remediation action threw error", { actionId: action.id, _error: String(_error), }); results.push({ vulnerabilityId: action.id, success: false, action: action.type, details: `Error: ${_error instanceof Error ? _error.message : String(_error)}`, timestamp: new Date(), }); } } this.remediationHistory.push(...results); const successCount = results.filter((r) => r.success).length; this.logger.info("Remediation plan completed", { planId: plan.planId, successCount, totalActions: results.length, successRate: `${Math.round((successCount / results.length) * 100)}%`, }); return results; } /** * Execute a single remediation action */ private async executeRemediationAction(action: RemediationAction): Promise<RemediationResult> { const startTime = Date.now(); try { let backupPath: string | undefined; // Create backup if enabled if (action.backup.enabled && action.target.file) { backupPath = await this.createBackup(action.target.file); } // Execute the action based on type switch (action.type) { case "replace": await this.executeReplaceAction(action); break; case "insert": await this.executeInsertAction(action); break; case "delete": await this.executeDeleteAction(action); break; case "config": await this.executeConfigAction(action); break; case "file": await this.executeFileAction(action); break; default: throw new Error(`Unknown action type: ${action.type}`); } // Validate the fix if validation is provided let validationResult: boolean | undefined; if (action.validation && action.target.file) { validationResult = await this.validateRemediation(action); } return { vulnerabilityId: action.id, success: true, action: action.type, details: `Remediation completed successfully in ${Date.now() - startTime}ms`, timestamp: new Date(), backupPath, validationResult, }; } catch (_error) { return { vulnerabilityId: action.id, success: false, action: action.type, details: `Remediation failed: ${_error instanceof Error ? _error.message : String(_error)}`, timestamp: new Date(), }; } } /** * Execute replace action */ private async executeReplaceAction(action: RemediationAction): Promise<void> { if (!action.target.file || !action.target.pattern || !action.replacement.content) { throw new Error("Replace action missing required fields"); } const content = await fs.readFile(action.target.file, "utf-8"); const updatedContent = content.replace(action.target.pattern, action.replacement.content); if (content === updatedContent) { throw new Error("No changes made - pattern not found"); } await fs.writeFile(action.target.file, updatedContent, "utf-8"); } /** * Execute insert action */ private async executeInsertAction(action: RemediationAction): Promise<void> { if (!action.target.file || !action.replacement.content) { throw new Error("Insert action missing required fields"); } const content = await fs.readFile(action.target.file, "utf-8"); const lines = content.split("\n"); const insertIndex = action.target.line ? Math.max(0, action.target.line - 1) : lines.length; lines.splice(insertIndex, 0, action.replacement.content); await fs.writeFile(action.target.file, lines.join("\n"), "utf-8"); } /** * Execute delete action */ private async executeDeleteAction(action: RemediationAction): Promise<void> { if (!action.target.file) { throw new Error("Delete action missing file"); } if (action.target.line) { // Delete specific line const content = await fs.readFile(action.target.file, "utf-8"); const lines = content.split("\n"); if (action.target.line > 0 && action.target.line <= lines.length) { lines.splice(action.target.line - 1, 1); await fs.writeFile(action.target.file, lines.join("\n"), "utf-8"); } } else if (action.target.pattern) { // Delete pattern matches const content = await fs.readFile(action.target.file, "utf-8"); const updatedContent = content.replace(action.target.pattern, ""); await fs.writeFile(action.target.file, updatedContent, "utf-8"); } else { throw new Error("Delete action missing target specification"); } } /** * Execute config action */ private async executeConfigAction(action: RemediationAction): Promise<void> { if (!action.target.file || !action.replacement.config) { throw new Error("Config action missing required fields"); } const content = await fs.readFile(action.target.file, "utf-8"); const config = JSON.parse(content); // Merge configuration changes Object.assign(config, action.replacement.config); await fs.writeFile(action.target.file, JSON.stringify(config, null, 2), "utf-8"); } /** * Execute file action */ private async executeFileAction(action: RemediationAction): Promise<void> { if (!action.replacement.action) { throw new Error("File action missing action specification"); } switch (action.replacement.action) { case "delete": if (action.target.file) { await fs.unlink(action.target.file); } break; case "create": if (action.target.file && action.replacement.content) { await fs.writeFile(action.target.file, action.replacement.content, "utf-8"); } break; default: throw new Error(`Unknown file action: ${action.replacement.action}`); } } /** * Create backup of file */ private async createBackup(filePath: string): Promise<string> { const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); const backupPath = path.join(this.backupDirectory, `${path.basename(filePath)}.${timestamp}.backup`); const content = await fs.readFile(filePath, "utf-8"); await fs.writeFile(backupPath, content, "utf-8"); this.logger.debug("Created backup file", { backupPath }); return backupPath; } /** * Ensure backup directory exists */ private async ensureBackupDirectory(): Promise<void> { try { await fs.access(this.backupDirectory); } catch { await fs.mkdir(this.backupDirectory, { recursive: true }); this.logger.info("Created backup directory", { directory: this.backupDirectory }); } } /** * Validate remediation */ private async validateRemediation(action: RemediationAction): Promise<boolean> { if (!action.validation?.test || !action.target.file) { return true; // No validation specified } try { // This is a simplified validation - in practice you'd run the test command const content = await fs.readFile(action.target.file, "utf-8"); // Simple pattern-based validation if (action.target.pattern) { const matches = content.match(action.target.pattern); return (matches?.length || 0) === (action.validation.expected || 0); } return true; } catch (_error) { this.logger.warn("Validation failed for action", { actionId: action.id, _error: String(_error), }); return false; } } /** * Simulate remediation actions for dry run */ private async simulateRemediationActions(actions: RemediationAction[]): Promise<RemediationResult[]> { const results: RemediationResult[] = []; for (const action of actions) { results.push({ vulnerabilityId: action.id, success: true, action: `${action.type} (simulated)`, details: `Would ${action.type} in ${action.target.file || "configuration"}`, timestamp: new Date(), }); } return results; } /** * Get remediation history */ getRemediationHistory(): RemediationResult[] { return [...this.remediationHistory]; } /** * Rollback remediation using backup */ async rollbackRemediation(backupPath: string, targetFile: string): Promise<void> { try { const backupContent = await fs.readFile(backupPath, "utf-8"); await fs.writeFile(targetFile, backupContent, "utf-8"); this.logger.info("Rolled back file from backup", { targetFile, backupPath, }); } catch (_error) { throw new SecurityValidationError( `Rollback failed: ${_error instanceof Error ? _error.message : String(_error)}`, ); } } /** * Clean up old backups */ async cleanupBackups(maxAge: number = 7 * 24 * 60 * 60 * 1000): Promise<void> { try { const files = await fs.readdir(this.backupDirectory); const now = Date.now(); for (const file of files) { const filePath = path.join(this.backupDirectory, file); const stats = await fs.stat(filePath); if (now - stats.mtime.getTime() > maxAge) { await fs.unlink(filePath); this.logger.debug("Cleaned up old backup", { file }); } } } catch (_error) { this.logger.warn("Backup cleanup failed", { _error: String(_error) }); } } }

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/docdyhr/mcp-wordpress'

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