Skip to main content
Glama
cbunting99

MCP Code Analysis & Quality Server

by cbunting99
SecurityAuditor.ts16.2 kB
// Copyright 2025 Chris Bunting // Brief: Handles security vulnerability scanning and auditing // Scope: Provides security analysis for dependencies including vulnerability detection import { Vulnerability, SeverityLevel } from '@mcp-code-analysis/shared-types'; import { Logger } from '../utils/Logger.js'; export interface SecurityAudit { package: string; version: string; vulnerabilities: Vulnerability[]; score: number; recommendations: string[]; lastChecked: Date; } export interface AdvisorySource { name: string; url: string; severity: SeverityLevel; description: string; } export interface LicenseInfo { name: string; url?: string; isPermissive: boolean; isCopyleft: boolean; requiresAttribution: boolean; } export class SecurityAuditor { private logger: Logger; private cache: Map<string, SecurityAudit>; // @ts-ignore - Field reserved for future use private _advisorySources: AdvisorySource[]; constructor(logger?: Logger) { this.logger = logger || new Logger(); this.cache = new Map(); this._advisorySources = [ { name: 'npm Advisory', url: 'https://www.npmjs.com/advisories', severity: SeverityLevel.ERROR, description: 'Node Package Manager Security Advisory Database', }, { name: 'GitHub Advisory', url: 'https://github.com/advisories', severity: SeverityLevel.WARNING, description: 'GitHub Security Advisory Database', }, { name: 'Snyk Vulnerability Database', url: 'https://snyk.io/vuln/', severity: SeverityLevel.WARNING, description: 'Snyk Vulnerability Database', }, { name: 'PyPI Security Advisories', url: 'https://pypi.org/security/', severity: SeverityLevel.ERROR, description: 'Python Package Index Security Advisories', }, { name: 'RustSec Advisory Database', url: 'https://rustsec.org/', severity: SeverityLevel.WARNING, description: 'Rust Security Advisory Database', }, ]; } async auditPackage( packageName: string, version: string, packageManager: string ): Promise<SecurityAudit> { this.logger.debug(`Auditing security for ${packageName}@${version}`); const cacheKey = `${packageName}:${version}:${packageManager}`; if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey)!; } const audit: SecurityAudit = { package: packageName, version, vulnerabilities: [], score: 0, recommendations: [], lastChecked: new Date(), }; try { // Check for vulnerabilities from various sources const vulnerabilities = await this.checkVulnerabilities(packageName, version, packageManager); audit.vulnerabilities = vulnerabilities; // Calculate security score audit.score = this.calculateSecurityScore(vulnerabilities); // Generate recommendations audit.recommendations = this.generateRecommendations(vulnerabilities, packageManager); // Cache the result this.cache.set(cacheKey, audit); this.logger.debug(`Security audit completed for ${packageName}@${version}: score ${audit.score}`); } catch (error) { this.logger.error(`Error auditing security for ${packageName}@${version}:`, error); audit.recommendations.push('Security audit failed. Please try again later.'); } return audit; } private async checkVulnerabilities( packageName: string, version: string, packageManager: string ): Promise<Vulnerability[]> { const vulnerabilities: Vulnerability[] = []; try { // This is a simplified implementation // In a real implementation, you would: // 1. Query actual vulnerability databases (npm audit, Snyk, etc.) // 2. Use APIs like GitHub Advisory, OSV, etc. // 3. Check multiple sources for comprehensive coverage // Simulate vulnerability detection for demo purposes const mockVulnerabilities = await this.getMockVulnerabilities(packageName, version, packageManager); vulnerabilities.push(...mockVulnerabilities); // Filter by severity if needed // This would be configurable based on user preferences } catch (error) { this.logger.error(`Error checking vulnerabilities for ${packageName}:`, error); } return vulnerabilities; } private async getMockVulnerabilities( packageName: string, version: string, _packageManager: string ): Promise<Vulnerability[]> { // This is a mock implementation for demonstration // In a real implementation, this would query actual vulnerability databases const vulnerabilities: Vulnerability[] = []; // Simulate some known vulnerabilities for common packages const knownVulnerabilities: Record<string, Array<{ versionRange: string; severity: SeverityLevel; description: string }>> = { 'lodash': [ { versionRange: '<4.17.12', severity: SeverityLevel.ERROR, description: 'Prototype Pollution in lodash', }, ], 'express': [ { versionRange: '<4.16.0', severity: SeverityLevel.WARNING, description: 'Open redirect vulnerability in Express', }, ], 'react': [ { versionRange: '<16.9.0', severity: SeverityLevel.WARNING, description: 'XSS vulnerability in React', }, ], 'axios': [ { versionRange: '<0.21.1', severity: SeverityLevel.ERROR, description: 'Server-Side Request Forgery in axios', }, ], }; const packageVulnerabilities = knownVulnerabilities[packageName.toLowerCase()]; if (packageVulnerabilities) { for (const vuln of packageVulnerabilities) { // Check if the current version is in the vulnerable range if (this.isVersionInRange(version, vuln.versionRange)) { vulnerabilities.push({ id: `${packageName}-${vuln.description.replace(/\s+/g, '-').toLowerCase()}`, severity: vuln.severity, description: vuln.description, fixedVersion: this.getFixedVersion(vuln.versionRange), cve: this.generateMockCVE(packageName, vuln.description), references: [ `https://www.npmjs.com/advisories/${packageName}-${vuln.description.replace(/\s+/g, '-')}`, 'https://cve.mitre.org/', ], }); } } } return vulnerabilities; } private isVersionInRange(version: string, range: string): boolean { // Simple version range checking // In a real implementation, use semver or similar library try { if (range.startsWith('<')) { const maxVersion = range.substring(1); return this.compareVersions(version, maxVersion) < 0; } else if (range.startsWith('<=')) { const maxVersion = range.substring(2); return this.compareVersions(version, maxVersion) <= 0; } else if (range.startsWith('>')) { const minVersion = range.substring(1); return this.compareVersions(version, minVersion) > 0; } else if (range.startsWith('>=')) { const minVersion = range.substring(2); return this.compareVersions(version, minVersion) >= 0; } } catch (error) { this.logger.error(`Error checking version range ${range}:`, error); } return false; } private compareVersions(version1: string, version2: string): number { // Simple version comparison // In a real implementation, use semver.compare const v1Parts = version1.split('.').map(Number); const v2Parts = version2.split('.').map(Number); for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) { const v1Part = v1Parts[i] || 0; const v2Part = v2Parts[i] || 0; if (v1Part > v2Part) return 1; if (v1Part < v2Part) return -1; } return 0; } private getFixedVersion(range: string): string { // Extract the fixed version from the range // This is a simplified implementation if (range.startsWith('<')) { return range.substring(1); } else if (range.startsWith('<=')) { return range.substring(2); } return 'latest'; } private generateMockCVE(packageName: string, description: string): string { // Generate a mock CVE ID for demonstration const hash = packageName.length + description.length; return `CVE-2024-${(hash % 9999).toString().padStart(4, '0')}`; } private calculateSecurityScore(vulnerabilities: Vulnerability[]): number { if (vulnerabilities.length === 0) { return 100; // Perfect score if no vulnerabilities } let score = 100; const severityWeights = { [SeverityLevel.ERROR]: 30, [SeverityLevel.WARNING]: 15, [SeverityLevel.INFO]: 5, [SeverityLevel.HINT]: 2, }; for (const vuln of vulnerabilities) { score -= severityWeights[vuln.severity]; } return Math.max(0, score); // Ensure score doesn't go below 0 } private generateRecommendations( vulnerabilities: Vulnerability[], packageManager: string ): string[] { const recommendations: string[] = []; if (vulnerabilities.length === 0) { recommendations.push('No security vulnerabilities found. Keep dependencies updated.'); return recommendations; } // Group vulnerabilities by severity const bySeverity = vulnerabilities.reduce((acc, vuln) => { if (!acc[vuln.severity]) { acc[vuln.severity] = []; } acc[vuln.severity].push(vuln); return acc; }, {} as Record<SeverityLevel, Vulnerability[]>); // Generate recommendations based on severity if (bySeverity[SeverityLevel.ERROR]) { recommendations.push( `🚨 CRITICAL: ${bySeverity[SeverityLevel.ERROR].length} critical vulnerabilities found. Update immediately!` ); } if (bySeverity[SeverityLevel.WARNING]) { recommendations.push( `⚠️ WARNING: ${bySeverity[SeverityLevel.WARNING].length} vulnerabilities found. Update recommended.` ); } // Package manager specific recommendations switch (packageManager) { case 'npm': case 'yarn': recommendations.push('Run `npm audit fix` or `yarn audit` to automatically fix vulnerabilities.'); break; case 'pip': recommendations.push('Use `pip-audit` or `safety check` to scan for vulnerabilities.'); break; case 'cargo': recommendations.push('Use `cargo audit` to check for Rust crate vulnerabilities.'); break; default: recommendations.push('Use your package manager\'s security audit tools.'); } // General recommendations recommendations.push('Regularly update dependencies to patch security vulnerabilities.'); recommendations.push('Consider using automated security scanning in your CI/CD pipeline.'); recommendations.push('Monitor security advisories for your dependencies.'); return recommendations; } async checkLicenseCompatibility( packageName: string, version: string, allowedLicenses: string[] ): Promise<{ compatible: boolean; license: LicenseInfo | null; issues: string[]; }> { this.logger.debug(`Checking license compatibility for ${packageName}@${version}`); const result = { compatible: true, license: null as LicenseInfo | null, issues: [] as string[], }; try { // Mock license detection const licenseInfo = await this.getLicenseInfo(packageName, version); result.license = licenseInfo; if (licenseInfo && !allowedLicenses.includes(licenseInfo.name)) { result.compatible = false; result.issues.push(`License "${licenseInfo.name}" is not in the allowed licenses list.`); } // Additional license checks if (licenseInfo?.isCopyleft) { result.issues.push('Package uses copyleft license which may require sharing your source code.'); } } catch (error) { this.logger.error(`Error checking license compatibility for ${packageName}:`, error); result.issues.push('License check failed. Please verify manually.'); } return result; } private async getLicenseInfo(_packageName: string, _version: string): Promise<LicenseInfo | null> { // Mock license detection // In a real implementation, this would query package registries or analyze package files const knownLicenses: Record<string, LicenseInfo> = { 'MIT': { name: 'MIT', isPermissive: true, isCopyleft: false, requiresAttribution: true, }, 'Apache-2.0': { name: 'Apache-2.0', isPermissive: true, isCopyleft: false, requiresAttribution: true, }, 'BSD-3-Clause': { name: 'BSD-3-Clause', isPermissive: true, isCopyleft: false, requiresAttribution: true, }, 'GPL-3.0': { name: 'GPL-3.0', isPermissive: false, isCopyleft: true, requiresAttribution: true, }, 'LGPL-2.1': { name: 'LGPL-2.1', isPermissive: false, isCopyleft: true, requiresAttribution: true, }, }; // Return a mock license for demonstration const licenseKeys = Object.keys(knownLicenses); const randomLicense = licenseKeys[Math.floor(Math.random() * licenseKeys.length)]; return knownLicenses[randomLicense] || null; } async generateSecurityReport( audits: SecurityAudit[] ): Promise<{ summary: { totalPackages: number; vulnerablePackages: number; criticalVulnerabilities: number; averageScore: number; }; recommendations: string[]; vulnerablePackages: Array<{ package: string; version: string; score: number; vulnerabilityCount: number; }>; }> { this.logger.debug('Generating security report'); const summary = { totalPackages: audits.length, vulnerablePackages: 0, criticalVulnerabilities: 0, averageScore: 0, }; const vulnerablePackages: Array<{ package: string; version: string; score: number; vulnerabilityCount: number; }> = []; let totalScore = 0; for (const audit of audits) { totalScore += audit.score; if (audit.vulnerabilities.length > 0) { summary.vulnerablePackages++; vulnerablePackages.push({ package: audit.package, version: audit.version, score: audit.score, vulnerabilityCount: audit.vulnerabilities.length, }); // Count critical vulnerabilities const criticalCount = audit.vulnerabilities.filter(v => v.severity === SeverityLevel.ERROR).length; summary.criticalVulnerabilities += criticalCount; } } summary.averageScore = audits.length > 0 ? totalScore / audits.length : 100; // Sort vulnerable packages by score (lowest first) vulnerablePackages.sort((a, b) => a.score - b.score); // Generate overall recommendations const recommendations: string[] = []; if (summary.vulnerablePackages > 0) { recommendations.push( `${summary.vulnerablePackages} packages have security vulnerabilities that need attention.` ); } if (summary.criticalVulnerabilities > 0) { recommendations.push( `🚨 ${summary.criticalVulnerabilities} critical vulnerabilities require immediate action.` ); } if (summary.averageScore < 80) { recommendations.push('Overall security score is below 80. Consider updating dependencies.'); } recommendations.push('Implement regular security scanning in your development workflow.'); recommendations.push('Set up automated alerts for new vulnerabilities in your dependencies.'); return { summary, recommendations, vulnerablePackages, }; } clearCache(): void { this.cache.clear(); this.logger.debug('Security auditor cache cleared'); } }

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/cbunting99/mcp-code-analysis-server'

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