Skip to main content
Glama
SecurityGateExecutor.ts15.9 kB
/** * Security Gate Executor * Handles execution of security gates and individual security checks */ import { LoggerFactory } from "@/utils/logger.js"; import type { AISecurityScanner } from "./AISecurityScanner.js"; import type { SecurityReviewer } from "./SecurityReviewer.js"; import type { SecurityConfigManager } from "./SecurityConfigManager.js"; import type { SecurityGate, SecurityCheck, GateResult, CheckResult, SecurityFinding, PipelineContext, GateExecutionOptions, CheckExecutionResult, GateStatusResult, } from "./SecurityTypes.js"; const logger = LoggerFactory.security(); /** * Dependencies for gate execution */ export interface GateExecutorDependencies { scanner: AISecurityScanner; reviewer: SecurityReviewer; configManager: SecurityConfigManager; } /** * Security Gate Executor * Responsible for executing security gates and their checks */ export class SecurityGateExecutor { private readonly scanner: AISecurityScanner; private readonly reviewer: SecurityReviewer; private readonly configManager: SecurityConfigManager; constructor(deps: GateExecutorDependencies) { this.scanner = deps.scanner; this.reviewer = deps.reviewer; this.configManager = deps.configManager; } /** * Execute a single security gate */ async executeGate( gate: SecurityGate, context: PipelineContext, options: GateExecutionOptions = {}, ): Promise<GateResult> { const startTime = Date.now(); const checkResults: CheckResult[] = []; for (const check of gate.checks) { if (!check.enabled) { continue; } logger.info(`Running check: ${check.name}`, { checkName: check.name }); try { const checkResult = await this.executeCheck(check, context, options); checkResults.push(checkResult); } catch (_error) { logger.error(`Check execution error: ${check.name}`, { checkName: check.name, _error }); checkResults.push({ checkId: check.id, checkName: check.name, status: "error", duration: Date.now() - startTime, findings: [], details: `Check execution failed: ${_error instanceof Error ? _error.message : String(_error)}`, score: 0, }); } } // Evaluate gate status based on check results and thresholds const gateStatus = this.evaluateGateStatus(gate, checkResults); return { gateId: gate.id, gateName: gate.id, status: gateStatus.status, duration: Date.now() - startTime, checks: checkResults, blocking: gate.blocking, message: gateStatus.message, }; } /** * Execute a single security check */ async executeCheck( check: SecurityCheck, context: PipelineContext, options: GateExecutionOptions = {}, ): Promise<CheckResult> { const startTime = Date.now(); const findings: SecurityFinding[] = []; let score: number = 100; let details = ""; if (options.dryRun) { return { checkId: check.id, checkName: check.name, status: "passed", duration: Date.now() - startTime, findings: [], details: "Dry run - no actual checks performed", score: 100, }; } try { let result: CheckExecutionResult; switch (check.type) { case "scan": result = await this.executeScanCheck(check, context); break; case "review": result = await this.executeReviewCheck(check, context); break; case "dependency": result = await this.executeDependencyCheck(check, context); break; case "configuration": result = await this.executeConfigurationCheck(check, context); break; case "secrets": result = await this.executeSecretsCheck(check, context); break; case "compliance": result = await this.executeComplianceCheck(check, context); break; default: throw new Error(`Unknown check type: ${check.type}`); } findings.push(...result.findings); score = result.score; details = result.details; // Determine check status based on findings const criticalCount = findings.filter((f) => f.severity === "critical").length; const highCount = findings.filter((f) => f.severity === "high").length; let status: CheckResult["status"]; if (criticalCount > 0) { status = "failed"; } else if (highCount > 0) { status = "warning"; } else { status = "passed"; } return { checkId: check.id, checkName: check.name, status, duration: Date.now() - startTime, findings, details, score, }; } catch (_error) { logger.warn(`Security check ${check.name} encountered error (converting to warning)`, { checkId: check.id, checkName: check.name, error: String(_error), errorStack: _error instanceof Error ? _error.stack : undefined, }); return { checkId: check.id, checkName: check.name, status: "warning", duration: Date.now() - startTime, findings: [], details: `Check execution issue treated as warning: ${String(_error)}`, score: Math.min(score ?? 100, 90), }; } } /** * Execute security scan check */ private async executeScanCheck(check: SecurityCheck, context: PipelineContext): Promise<CheckExecutionResult> { const scanParams = check.parameters as { targets?: string[]; depth?: "shallow" | "deep" | "comprehensive"; includeRuntime?: boolean; includeFileSystem?: boolean; }; const scannerAny = this.scanner as unknown as { scanCodeForVulnerabilities?: () => Promise<unknown>; performScan?: (opts?: unknown) => Promise<unknown>; }; let scanResult: unknown; if (typeof scannerAny.scanCodeForVulnerabilities === "function") { scanResult = await scannerAny.scanCodeForVulnerabilities(); } else if (typeof scannerAny.performScan === "function") { scanResult = await scannerAny.performScan({ targets: scanParams.targets ?? ["src/"], depth: scanParams.depth ?? "deep", includeRuntime: scanParams.includeRuntime ?? false, includeFileSystem: scanParams.includeFileSystem ?? true, }); } else { scanResult = { vulnerabilities: [], summary: { total: 0, critical: 0, high: 0, medium: 0 } }; } const scanResultTyped = scanResult as | { vulnerabilities?: unknown[]; summary?: Record<string, unknown> } | null | undefined; const vulns = Array.isArray(scanResultTyped?.vulnerabilities) ? scanResultTyped.vulnerabilities : []; const summary = scanResultTyped?.summary ? scanResultTyped.summary : { total: vulns.length, critical: vulns.filter((v: unknown) => (v as { severity?: string })?.severity === "critical").length, high: vulns.filter((v: unknown) => (v as { severity?: string })?.severity === "high").length, medium: vulns.filter((v: unknown) => (v as { severity?: string })?.severity === "medium").length, }; const findings: SecurityFinding[] = (vulns || []).map((vuln: unknown) => { const v = vuln as { id?: string; severity?: string; type?: string; description?: string; location?: { file?: string; line?: number }; remediation?: { suggested?: string }; }; return { id: v.id || "unknown", severity: (v.severity as SecurityFinding["severity"]) || "medium", type: v.type || "vulnerability", description: v.description || "No description", file: v.location?.file, line: v.location?.line, remediation: v.remediation?.suggested, }; }); const summaryTyped = summary as { critical?: number; high?: number; medium?: number }; const score = Math.max( 0, 100 - ((summaryTyped.critical || 0) * 10 + (summaryTyped.high || 0) * 5 + (summaryTyped.medium || 0) * 2), ); return { findings, score, details: `Scanned codebase: ${summary.total} vulnerabilities found`, }; } /** * Execute code review check */ private async executeReviewCheck(check: SecurityCheck, context: PipelineContext): Promise<CheckExecutionResult> { const reviewParams = check.parameters as { rules?: string[]; excludeRules?: string[]; aiAnalysis?: boolean }; const reviewerAny = this.reviewer as unknown as { reviewDirectory?: (path: string, opts?: unknown) => Promise<unknown>; reviewCode?: (path: string, opts?: unknown) => Promise<unknown>; }; const raw = typeof reviewerAny.reviewDirectory === "function" ? await reviewerAny.reviewDirectory("src/", { recursive: true, rules: reviewParams.rules ?? [], excludeRules: reviewParams.excludeRules ?? [], aiAnalysis: reviewParams.aiAnalysis ?? false, }) : typeof reviewerAny.reviewCode === "function" ? await reviewerAny.reviewCode("src/", { rules: reviewParams.rules ?? [], aiAnalysis: reviewParams.aiAnalysis ?? false, }) : []; const reviewResults = Array.isArray(raw) ? raw : raw ? [raw] : []; const allFindings: SecurityFinding[] = []; let totalScore = 0; for (const result of reviewResults) { const resultTyped = result as { findings?: unknown[]; file?: string }; const resultFindings = (resultTyped.findings || []).map((finding: unknown) => { const f = finding as { id?: string; severity?: string; category?: string; type?: string; message?: string; description?: string; line?: number; recommendation?: string; remediation?: string; }; return { id: f.id || "unknown", severity: (f.severity as SecurityFinding["severity"]) || "medium", type: f.category || f.type || "review", description: f.message || f.description || "No description", file: resultTyped.file, line: f.line, remediation: f.recommendation || f.remediation, }; }); allFindings.push(...resultFindings); totalScore += this.calculateFileScore((resultTyped.findings || []) as Array<{ severity?: string }>); } const averageScore = reviewResults.length > 0 ? totalScore / reviewResults.length : 100; return { findings: allFindings, score: averageScore, details: `Reviewed ${reviewResults.length} files: ${allFindings.length} security issues found`, }; } /** * Execute dependency check */ private async executeDependencyCheck(check: SecurityCheck, context: PipelineContext): Promise<CheckExecutionResult> { logger.info("Dependency check - integration with external tools required"); return { findings: [], score: 100, details: "Dependency check completed - no vulnerabilities found", }; } /** * Execute configuration check */ private async executeConfigurationCheck( check: SecurityCheck, context: PipelineContext, ): Promise<CheckExecutionResult> { let compliance: { compliant: boolean; violations: string[] } = { compliant: true, violations: [] }; try { const configManager = this.configManager as unknown as Record<string, unknown> & { validateCompliance?: (env: string) => Promise<{ compliant: boolean; violations: string[] }>; getSecurityConfig?: () => Record<string, unknown> & { compliant?: boolean; violations?: string[] }; }; if (typeof configManager.validateCompliance === "function") { compliance = await configManager.validateCompliance(context.environment); } else if (typeof configManager.getSecurityConfig === "function") { const cfg = configManager.getSecurityConfig() || {}; compliance = { compliant: !!cfg.compliant, violations: cfg.violations ?? [] }; } } catch (_e) { compliance = { compliant: true, violations: [] }; } const findings: SecurityFinding[] = compliance.violations.map((violation, index) => ({ id: `config-${index}`, severity: "medium" as const, type: "Configuration", description: violation, remediation: "Review security configuration", })); const score = compliance.compliant ? 100 : Math.max(0, 100 - compliance.violations.length * 10); return { findings, score, details: `Configuration compliance: ${compliance.compliant ? "compliant" : "non-compliant"}`, }; } /** * Execute secrets check */ private async executeSecretsCheck(check: SecurityCheck, context: PipelineContext): Promise<CheckExecutionResult> { logger.info("Secrets check - integration with secret scanning tools required"); return { findings: [], score: 100, details: "Secrets scan completed - no exposed secrets found", }; } /** * Execute compliance check */ private async executeComplianceCheck(check: SecurityCheck, context: PipelineContext): Promise<CheckExecutionResult> { const complianceParams = check.parameters as { frameworks?: string[] }; const frameworks: string[] = complianceParams.frameworks ?? ["OWASP", "CWE"]; for (const framework of frameworks) { logger.info(`Checking ${framework} compliance`, { framework }); } return { findings: [], score: 100, details: `Compliance check completed for frameworks: ${frameworks.join(", ")}`, }; } /** * Calculate security score for file findings */ private calculateFileScore(findings: Array<{ severity?: string }>): number { const severityWeights: Record<string, number> = { critical: 20, high: 10, medium: 5, low: 2, info: 1 }; const penalty = findings.reduce((sum: number, finding) => { return sum + (severityWeights[finding.severity || "info"] || 0); }, 0); return Math.max(0, 100 - penalty); } /** * Evaluate gate status based on check results and thresholds */ evaluateGateStatus(gate: SecurityGate, checkResults: CheckResult[]): GateStatusResult { const allFindings = checkResults.flatMap((result) => result.findings); const criticalCount = allFindings.filter((f) => f.severity === "critical").length; const highCount = allFindings.filter((f) => f.severity === "high").length; const mediumCount = allFindings.filter((f) => f.severity === "medium").length; const validChecks = checkResults.filter((result) => result.status !== "error"); const averageScore = validChecks.length > 0 ? validChecks.reduce((sum, result) => sum + result.score, 0) / validChecks.length : 100; if (criticalCount > gate.thresholds.maxCritical) { return { status: "failed", message: `Critical vulnerabilities (${criticalCount}) exceed threshold (${gate.thresholds.maxCritical})`, }; } if (highCount > gate.thresholds.maxHigh) { return { status: "failed", message: `High-severity vulnerabilities (${highCount}) exceed threshold (${gate.thresholds.maxHigh})`, }; } if (averageScore < gate.thresholds.minSecurityScore) { return { status: "failed", message: `Security score (${averageScore.toFixed(1)}) below threshold (${gate.thresholds.minSecurityScore})`, }; } if (mediumCount > gate.thresholds.maxMedium) { return { status: "warning", message: `Medium-severity vulnerabilities (${mediumCount}) exceed threshold (${gate.thresholds.maxMedium})`, }; } return { status: "passed", message: "All security checks passed", }; } }

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