Skip to main content
Glama
SecurityReviewer.ts28.4 kB
/** * AI-Powered Security Code Reviewer * Provides intelligent security code review and analysis */ import * as fs from "fs/promises"; import * as path from "path"; import { SecurityUtils } from "./SecurityConfig.js"; import { SecurityValidationError } from "./InputValidator.js"; import { LoggerFactory } from "@/utils/logger.js"; interface SecurityReviewRule { id: string; name: string; description: string; category: "authentication" | "authorization" | "input-validation" | "crypto" | "session" | "config" | "general"; severity: "critical" | "high" | "medium" | "low" | "info"; pattern: RegExp; message: string; recommendation: string; cweId?: string; examples: { vulnerable: string; secure: string; }; } export interface CodeReviewResult { reviewId: string; timestamp: Date; file: string; findings: SecurityFinding[]; summary: { totalFindings: number; criticalFindings: number; highFindings: number; mediumFindings: number; lowFindings: number; infoFindings: number; }; overallRating: "secure" | "needs-review" | "vulnerable" | "critical"; recommendations: string[]; } interface SecurityFinding { id: string; rule: string; severity: "critical" | "high" | "medium" | "low" | "info"; line: number; column: number; code: string; message: string; recommendation: string; confidence: number; category: string; } interface AICodeAnalysis { complexity: number; securityScore: number; patterns: { positive: string[]; negative: string[]; }; recommendations: string[]; riskAssessment: { level: "low" | "medium" | "high" | "critical"; factors: string[]; }; } /** * Comprehensive security review rules */ const SECURITY_REVIEW_RULES: SecurityReviewRule[] = [ // Authentication Rules { id: "auth-001", name: "Hardcoded Credentials", description: "Detects hardcoded passwords, API keys, and secrets", category: "authentication", severity: "critical", pattern: /(password|secret|key|token)\s*[:=]\s*['"][^'"]{8,}['"]/gi, message: "Hardcoded credential detected", recommendation: "Use environment variables or secure credential storage", cweId: "CWE-798", examples: { vulnerable: `const password = "mysecretpassword123";`, secure: `const password = process.env.DB_PASSWORD;`, }, }, { id: "auth-002", name: "Weak Password Policy", description: "Detects weak password validation", category: "authentication", severity: "medium", pattern: /password.*length.*[<>]\s*[1-7]\b/gi, message: "Weak password length requirement", recommendation: "Enforce minimum 8-character passwords with complexity requirements", cweId: "CWE-521", examples: { vulnerable: `if (password.length < 6) return false;`, secure: `if (password.length < 8 || !/(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)/.test(password)) return false;`, }, }, { id: "auth-003", name: "JWT Secret Exposure", description: "Detects exposed JWT secrets", category: "authentication", severity: "critical", pattern: /jwt.*secret.*[:=].*['"][^'"]+['"]/gi, message: "JWT secret should not be hardcoded", recommendation: "Store JWT secret in environment variables", cweId: "CWE-798", examples: { vulnerable: `const jwtSecret = "my-jwt-secret-key";`, secure: `const jwtSecret = process.env.JWT_SECRET;`, }, }, // Input Validation Rules { id: "input-001", name: "SQL Injection Risk", description: "Detects potential SQL injection vulnerabilities", category: "input-validation", severity: "critical", pattern: /(SELECT|INSERT|UPDATE|DELETE).*?[\+].*?(WHERE|FROM|INTO)/gi, message: "Potential SQL injection vulnerability", recommendation: "Use parameterized queries or prepared statements", cweId: "CWE-89", examples: { vulnerable: `query = "SELECT * FROM users WHERE id = " + userId;`, secure: `query = "SELECT * FROM users WHERE id = ?"; db.query(query, [userId]);`, }, }, { id: "input-002", name: "XSS Vulnerability", description: "Detects potential XSS vulnerabilities", category: "input-validation", severity: "high", pattern: /innerHTML\s*=\s*[^;]+userInput/gi, message: "Potential XSS vulnerability through innerHTML", recommendation: "Use textContent or sanitize input before setting innerHTML", cweId: "CWE-79", examples: { vulnerable: `element.innerHTML = userInput;`, secure: `element.textContent = userInput; // or sanitize userInput`, }, }, { id: "input-003", name: "Command Injection", description: "Detects potential command injection vulnerabilities", category: "input-validation", severity: "critical", pattern: /exec\(.*?[\+].*?\)/gi, message: "Potential command injection vulnerability", recommendation: "Validate input and use safe APIs", cweId: "CWE-78", examples: { vulnerable: `exec("ls " + userInput);`, secure: `execFile("ls", [userInput]);`, }, }, // Authorization Rules { id: "authz-001", name: "Missing Authorization Check", description: "Detects endpoints without authorization checks", category: "authorization", severity: "high", pattern: /app\.(get|post|put|delete)\(['"][^'"]*['"],\s*(?!.*auth|.*login)[^)]*\)/gi, message: "Endpoint may be missing authorization check", recommendation: "Add authorization middleware to protected endpoints", cweId: "CWE-862", examples: { vulnerable: `app.get("/admin/users", (req, res) => { ... });`, secure: `app.get("/admin/users", authMiddleware, (req, res) => { ... });`, }, }, { id: "authz-002", name: "Privilege Escalation Risk", description: "Detects potential privilege escalation", category: "authorization", severity: "high", pattern: /role\s*=\s*['"]admin['"]|isAdmin\s*=\s*true/gi, message: "Potential privilege escalation through role assignment", recommendation: "Validate user permissions before role assignment", cweId: "CWE-269", examples: { vulnerable: `user.role = "admin";`, secure: `if (currentUser.canAssignRole("admin")) user.role = "admin";`, }, }, // Cryptography Rules { id: "crypto-001", name: "Weak Encryption Algorithm", description: "Detects use of weak encryption algorithms", category: "crypto", severity: "high", pattern: /(md5|sha1|des|rc4|3des)/gi, message: "Weak cryptographic algorithm detected", recommendation: "Use strong algorithms like AES-256, SHA-256, or bcrypt", cweId: "CWE-327", examples: { vulnerable: `const hash = crypto.createHash("md5");`, secure: `const hash = crypto.createHash("sha256");`, }, }, { id: "crypto-002", name: "Hardcoded Encryption Key", description: "Detects hardcoded encryption keys", category: "crypto", severity: "critical", pattern: /encrypt.*key.*[:=].*['"][^'"]{16,}['"]/gi, message: "Hardcoded encryption key detected", recommendation: "Generate keys securely and store in environment variables", cweId: "CWE-798", examples: { vulnerable: `const encryptionKey = "1234567890abcdef";`, secure: `const encryptionKey = process.env.ENCRYPTION_KEY;`, }, }, // Session Management Rules { id: "session-001", name: "Insecure Session Configuration", description: "Detects insecure session settings", category: "session", severity: "medium", pattern: /session.*secure\s*:\s*false|httpOnly\s*:\s*false/gi, message: "Insecure session configuration", recommendation: "Enable secure and httpOnly flags for sessions", cweId: "CWE-614", examples: { vulnerable: `session({ secure: false, httpOnly: false })`, secure: `session({ secure: true, httpOnly: true })`, }, }, { id: "session-002", name: "Session Fixation Risk", description: "Detects potential session fixation vulnerabilities", category: "session", severity: "medium", pattern: /login.*(?!regenerate)/gi, message: "Login may not regenerate session ID", recommendation: "Regenerate session ID after successful login", cweId: "CWE-384", examples: { vulnerable: `// Login without session regeneration`, secure: `req.session.regenerate(() => { /* login success */ });`, }, }, // Configuration Rules { id: "config-001", name: "Debug Mode in Production", description: "Detects debug mode enabled", category: "config", severity: "medium", pattern: /debug\s*[:=]\s*true|DEBUG\s*=\s*['"]?true['"]?/gi, message: "Debug mode may be enabled in production", recommendation: "Disable debug mode in production environments", cweId: "CWE-489", examples: { vulnerable: `const debug = true;`, secure: `const debug = process.env.NODE_ENV !== 'production';`, }, }, { id: "config-002", name: "HTTPS Disabled", description: "Detects HTTP instead of HTTPS", category: "config", severity: "medium", pattern: /http:\/\/(?!localhost|127\.0\.0\.1)/gi, message: "HTTP URL detected in production code", recommendation: "Use HTTPS for all external communications", cweId: "CWE-319", examples: { vulnerable: `const apiUrl = "http://api.example.com";`, secure: `const apiUrl = "https://api.example.com";`, }, }, // General Security Rules { id: "general-001", name: "Error Information Disclosure", description: "Detects potential information disclosure through errors", category: "general", severity: "low", pattern: /throw.*error.*stack|console\.error.*stack/gi, message: "Error may disclose sensitive information", recommendation: "Log detailed errors securely, return generic errors to users", cweId: "CWE-209", examples: { vulnerable: `throw new Error(err.stack);`, secure: `logger.error(err.stack); throw new Error("An error occurred");`, }, }, { id: "general-002", name: "Unsafe Random Generation", description: "Detects use of unsafe random number generation", category: "general", severity: "medium", pattern: /Math\.random\(\)/gi, message: "Math.random() is not cryptographically secure", recommendation: "Use crypto.randomBytes() for security-sensitive random generation", cweId: "CWE-338", examples: { vulnerable: `const token = Math.random().toString(36);`, secure: `const token = crypto.randomBytes(32).toString('hex');`, }, }, ]; /** * AI-Powered Security Code Reviewer */ export class SecurityReviewer { private reviewHistory: CodeReviewResult[] = []; /** * Perform comprehensive security review of a file */ async reviewFile( filePath: string, options: { rules?: string[]; excludeRules?: string[]; aiAnalysis?: boolean; } = {}, ): Promise<CodeReviewResult> { const reviewId = SecurityUtils.generateSecureToken(16); const logger = LoggerFactory.security(); logger.info("Reviewing file", { filePath, reviewId }); try { const content = await fs.readFile(filePath, "utf-8"); const lines = content.split("\n"); // Apply security rules const findings = await this.applySecurityRules(content, lines, options); // Perform AI analysis if requested let aiAnalysis: AICodeAnalysis | undefined; if (options.aiAnalysis) { aiAnalysis = await this.performAIAnalysis(content, findings); } // Generate summary const summary = this.generateSummary(findings); const overallRating = this.calculateOverallRating(summary); const recommendations = this.generateRecommendations(findings, aiAnalysis); const result: CodeReviewResult = { reviewId, timestamp: new Date(), file: filePath, findings, summary, overallRating, recommendations, }; this.reviewHistory.push(result); logger.info("Review completed", { filePath, findingsCount: findings.length, reviewId }); return result; } catch (_error) { logger.error("Review failed", { filePath, reviewId, _error: _error instanceof Error ? _error.message : String(_error), }); throw new SecurityValidationError("Security review failed", [{ message: String(_error) }]); } } /** * Review multiple files */ async reviewDirectory( dirPath: string, options: { recursive?: boolean; filePattern?: RegExp; rules?: string[]; excludeRules?: string[]; aiAnalysis?: boolean; } = {}, ): Promise<CodeReviewResult[]> { const logger = LoggerFactory.security(); logger.info("Reviewing directory", { dirPath }); const results: CodeReviewResult[] = []; const filePattern = options.filePattern || /\.(ts|js|jsx|tsx)$/; try { const entries = await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); if (entry.isDirectory() && options.recursive && !entry.name.startsWith(".")) { const subResults = await this.reviewDirectory(fullPath, options); results.push(...subResults); } else if (entry.isFile() && filePattern.test(entry.name)) { const result = await this.reviewFile(fullPath, options); results.push(result); } } return results; } catch (_error) { logger.error("Directory review failed", { dirPath, _error: _error instanceof Error ? _error.message : String(_error), }); throw new SecurityValidationError("Directory review failed", [{ message: String(_error) }]); } } /** * Apply security rules to code content */ private async applySecurityRules( content: string, lines: string[], options: { rules?: string[]; excludeRules?: string[]; }, ): Promise<SecurityFinding[]> { const findings: SecurityFinding[] = []; const applicableRules = this.getApplicableRules(options); for (const rule of applicableRules) { const matches = Array.from(content.matchAll(rule.pattern)); for (const match of matches) { if (match.index !== undefined) { const lineNumber = this.getLineNumber(content, match.index); const columnNumber = this.getColumnNumber(content, match.index); findings.push({ id: `${rule.id}-${Date.now()}-${findings.length}`, rule: rule.id, severity: rule.severity, line: lineNumber, column: columnNumber, code: lines[lineNumber - 1]?.trim() || "", message: rule.message, recommendation: rule.recommendation, confidence: this.calculateConfidence(rule, match[0]), category: rule.category, }); } } } return findings; } /** * Get applicable security rules based on options */ private getApplicableRules(options: { rules?: string[]; excludeRules?: string[] }): SecurityReviewRule[] { let rules = SECURITY_REVIEW_RULES; if (options.rules) { rules = rules.filter((rule) => options.rules!.includes(rule.id)); } if (options.excludeRules) { rules = rules.filter((rule) => !options.excludeRules!.includes(rule.id)); } return rules; } /** * Perform AI-powered code analysis */ private async performAIAnalysis(content: string, findings: SecurityFinding[]): Promise<AICodeAnalysis> { // Simplified AI analysis - in practice this would use machine learning const complexity = this.calculateComplexity(content); const securityScore = this.calculateSecurityScore(findings, content.length); const patterns = this.analyzePatterns(content); const recommendations = this.generateAIRecommendations(findings, patterns); const riskAssessment = this.assessRisk(findings, complexity); return { complexity, securityScore, patterns, recommendations, riskAssessment, }; } /** * Calculate code complexity */ private calculateComplexity(content: string): number { const complexityFactors = [ (content.match(/if\s*\(/g) || []).length * 1, (content.match(/for\s*\(/g) || []).length * 2, (content.match(/while\s*\(/g) || []).length * 2, (content.match(/switch\s*\(/g) || []).length * 3, (content.match(/try\s*\{/g) || []).length * 2, (content.match(/catch\s*\(/g) || []).length * 2, ]; return complexityFactors.reduce((sum, factor) => sum + factor, 0); } /** * Calculate security score */ private calculateSecurityScore(findings: SecurityFinding[], codeLength: number): number { const severityWeights = { critical: 10, high: 7, medium: 4, low: 2, info: 1 }; const penalty = findings.reduce((sum, finding) => { return sum + severityWeights[finding.severity]; }, 0); const normalizedPenalty = penalty / (codeLength / 1000); // Normalize by code size return Math.max(0, 100 - normalizedPenalty); } /** * Analyze code patterns */ private analyzePatterns(content: string): { positive: string[]; negative: string[] } { const positive: string[] = []; const negative: string[] = []; // Positive patterns if (/process\.env\./g.test(content)) positive.push("Uses environment variables"); if (/try\s*\{[\s\S]*catch/g.test(content)) positive.push("Implements error handling"); if (/crypto\.randomBytes/g.test(content)) positive.push("Uses secure random generation"); if (/bcrypt/g.test(content)) positive.push("Uses secure password hashing"); if (/https:\/\//g.test(content)) positive.push("Uses HTTPS URLs"); // Negative patterns if (/eval\s*\(/g.test(content)) negative.push("Uses dangerous eval() function"); if (/innerHTML.*\+/g.test(content)) negative.push("Potential XSS through innerHTML concatenation"); if (/password.*=.*['"][^'"]{1,7}['"]/g.test(content)) negative.push("Weak passwords detected"); if (/http:\/\/(?!localhost)/g.test(content)) negative.push("Uses insecure HTTP URLs"); return { positive, negative }; } /** * Generate AI recommendations */ private generateAIRecommendations( findings: SecurityFinding[], patterns: { positive: string[]; negative: string[] }, ): string[] { const recommendations: string[] = []; // High-level recommendations based on findings const criticalFindings = findings.filter((f) => f.severity === "critical"); if (criticalFindings.length > 0) { recommendations.push("Address critical security vulnerabilities immediately"); } const authFindings = findings.filter((f) => f.category === "authentication"); if (authFindings.length > 2) { recommendations.push("Review and strengthen authentication mechanisms"); } const inputFindings = findings.filter((f) => f.category === "input-validation"); if (inputFindings.length > 0) { recommendations.push("Implement comprehensive input validation and sanitization"); } // Recommendations based on patterns if (patterns.negative.length > patterns.positive.length) { recommendations.push("Consider refactoring to follow security best practices"); } if (patterns.negative.includes("Uses dangerous eval() function")) { recommendations.push("Replace eval() with safer alternatives like JSON.parse()"); } return recommendations; } /** * Assess overall risk */ private assessRisk( findings: SecurityFinding[], complexity: number, ): { level: "low" | "medium" | "high" | "critical"; factors: string[] } { const factors: string[] = []; let riskScore = 0; // Risk based on findings const criticalCount = findings.filter((f) => f.severity === "critical").length; const highCount = findings.filter((f) => f.severity === "high").length; riskScore += criticalCount * 10 + highCount * 5; if (criticalCount > 0) factors.push(`${criticalCount} critical vulnerabilities`); if (highCount > 0) factors.push(`${highCount} high-severity vulnerabilities`); // Risk based on complexity if (complexity > 50) { riskScore += 10; factors.push("High code complexity"); } // Risk based on categories const authVulns = findings.filter((f) => f.category === "authentication").length; if (authVulns > 0) { riskScore += authVulns * 3; factors.push("Authentication vulnerabilities"); } // Determine risk level let level: "low" | "medium" | "high" | "critical"; if (riskScore >= 30) level = "critical"; else if (riskScore >= 20) level = "high"; else if (riskScore >= 10) level = "medium"; else level = "low"; return { level, factors }; } /** * Calculate confidence score for a finding */ private calculateConfidence(rule: SecurityReviewRule, match: string): number { // Base confidence varies by rule type let confidence = 0.7; // Adjust based on match characteristics if (match.length > 50) confidence += 0.1; // Longer matches are more likely to be intentional if (/\w{20,}/.test(match)) confidence += 0.1; // Long strings suggest real credentials if (rule.category === "authentication") confidence += 0.1; // Auth issues are commonly overlooked return Math.min(1.0, confidence); } /** * Generate summary of findings */ private generateSummary(findings: SecurityFinding[]): { totalFindings: number; criticalFindings: number; highFindings: number; mediumFindings: number; lowFindings: number; infoFindings: number; } { return { totalFindings: findings.length, criticalFindings: findings.filter((f) => f.severity === "critical").length, highFindings: findings.filter((f) => f.severity === "high").length, mediumFindings: findings.filter((f) => f.severity === "medium").length, lowFindings: findings.filter((f) => f.severity === "low").length, infoFindings: findings.filter((f) => f.severity === "info").length, }; } /** * Calculate overall security rating */ private calculateOverallRating(summary: { criticalFindings: number; highFindings: number; mediumFindings: number; lowFindings: number; }): "secure" | "needs-review" | "vulnerable" | "critical" { if (summary.criticalFindings > 0) return "critical"; if (summary.highFindings > 2) return "vulnerable"; if (summary.highFindings > 0 || summary.mediumFindings > 3) return "needs-review"; return "secure"; } /** * Generate recommendations based on findings */ private generateRecommendations(findings: SecurityFinding[], aiAnalysis?: AICodeAnalysis): string[] { const recommendations: string[] = []; // Priority recommendations based on severity const criticalFindings = findings.filter((f) => f.severity === "critical"); if (criticalFindings.length > 0) { recommendations.push("Immediately address critical security vulnerabilities"); recommendations.push("Review credential management and move secrets to environment variables"); } // Category-specific recommendations const categories = [...new Set(findings.map((f) => f.category))]; if (categories.includes("authentication")) { recommendations.push("Strengthen authentication mechanisms and credential handling"); } if (categories.includes("input-validation")) { recommendations.push("Implement comprehensive input validation and output encoding"); } if (categories.includes("crypto")) { recommendations.push("Update to use strong cryptographic algorithms and secure key management"); } // Add AI recommendations if available if (aiAnalysis) { recommendations.push(...aiAnalysis.recommendations); } return [...new Set(recommendations)]; // Remove duplicates } /** * Get line number from character index */ private getLineNumber(content: string, index: number): number { return content.substring(0, index).split("\n").length; } /** * Get column number from character index */ private getColumnNumber(content: string, index: number): number { const lines = content.substring(0, index).split("\n"); return lines[lines.length - 1].length + 1; } /** * Get review history */ getReviewHistory(): CodeReviewResult[] { return [...this.reviewHistory]; } /** * Get security rules */ getSecurityRules(): SecurityReviewRule[] { return [...SECURITY_REVIEW_RULES]; } /** * Add custom security rule */ addCustomRule(rule: SecurityReviewRule): void { SECURITY_REVIEW_RULES.push(rule); } /** * Remove security rule */ removeRule(ruleId: string): boolean { const index = SECURITY_REVIEW_RULES.findIndex((rule) => rule.id === ruleId); if (index !== -1) { SECURITY_REVIEW_RULES.splice(index, 1); return true; } return false; } /** * Generate security report */ generateSecurityReport(results: CodeReviewResult[]): { summary: { filesReviewed: number; totalFindings: number; criticalFindings: number; highFindings: number; overallRating: "secure" | "needs-review" | "vulnerable" | "critical"; }; topIssues: SecurityFinding[]; recommendations: string[]; riskFactors: string[]; } { const summary = { filesReviewed: results.length, totalFindings: results.reduce((sum, r) => sum + r.findings.length, 0), criticalFindings: results.reduce((sum, r) => sum + r.summary.criticalFindings, 0), highFindings: results.reduce((sum, r) => sum + r.summary.highFindings, 0), overallRating: this.calculateProjectRating(results), }; const allFindings = results.flatMap((r) => r.findings); const topIssues = allFindings .filter((f) => f.severity === "critical" || f.severity === "high") .sort((a, b) => { const severityOrder = { critical: 4, high: 3, medium: 2, low: 1, info: 0 }; return severityOrder[b.severity] - severityOrder[a.severity]; }) .slice(0, 10); const recommendations = [...new Set(results.flatMap((r) => r.recommendations))]; const riskFactors = this.identifyRiskFactors(results); return { summary, topIssues, recommendations, riskFactors, }; } /** * Calculate overall project security rating */ private calculateProjectRating(results: CodeReviewResult[]): "secure" | "needs-review" | "vulnerable" | "critical" { const criticalCount = results.reduce((sum, r) => sum + r.summary.criticalFindings, 0); const highCount = results.reduce((sum, r) => sum + r.summary.highFindings, 0); const criticalFiles = results.filter((r) => r.overallRating === "critical").length; if (criticalCount > 0 || criticalFiles > 0) return "critical"; if (highCount > 5 || results.filter((r) => r.overallRating === "vulnerable").length > 2) return "vulnerable"; if (highCount > 0 || results.filter((r) => r.overallRating === "needs-review").length > 0) return "needs-review"; return "secure"; } /** * Identify project-wide risk factors */ private identifyRiskFactors(results: CodeReviewResult[]): string[] { const factors: string[] = []; const allFindings = results.flatMap((r) => r.findings); const authIssues = allFindings.filter((f) => f.category === "authentication").length; if (authIssues > 3) factors.push("Multiple authentication vulnerabilities"); const cryptoIssues = allFindings.filter((f) => f.category === "crypto").length; if (cryptoIssues > 0) factors.push("Cryptographic implementation issues"); const inputIssues = allFindings.filter((f) => f.category === "input-validation").length; if (inputIssues > 5) factors.push("Widespread input validation issues"); const vulnerableFiles = results.filter( (r) => r.overallRating === "vulnerable" || r.overallRating === "critical", ).length; const totalFiles = results.length; if (vulnerableFiles / totalFiles > 0.3) factors.push("High percentage of vulnerable files"); return factors; } }

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