UNPKG

mcp-cve-intelligence-server-lite-test

Version:

Lite Model Context Protocol server for comprehensive CVE intelligence gathering with multi-source exploit discovery, designed for security professionals and cybersecurity researchers - Alpha Release

583 lines 26 kB
import { BaseCVESourceImplementation } from './base.js'; import { createContextLogger } from '../utils/logger.js'; import { normalizeDescriptions } from '../utils/cve-utils.js'; import { getAppConfiguration } from '../config/index.js'; const logger = createContextLogger('MITRESource'); /** * MITRE Corporation CVE Source Implementation * * MITRE is the original CVE numbering authority and maintains the official CVE Services API. * This implementation provides access to authoritative CVE records directly from the source. * * API Documentation: https://cveawg.mitre.org/api-docs/ * Base URL: https://cveawg.mitre.org/api * * IMPLEMENTED ENDPOINTS: * * Public Access (no authentication): * - GET /cve/{id} - Retrieve individual CVE records by ID * * Authenticated Access (requires CVE Program membership): * - GET /cve - Search/filter CVE records (Secretariat role) * - GET /cve_cursor - Cursor-based pagination (Secretariat role) * * AUTHENTICATION: * MITRE uses a three-header authentication system for CVE Program members: * - CVE-API-ORG: Organization short name (env: MITRE_API_ORG or CVE_API_ORG) * - CVE-API-USER: Username (env: MITRE_API_USER or CVE_API_USER) * - CVE-API-KEY: API key (env: MITRE_API_KEY) * * FEATURES: * Individual CVE lookup (public) * Authenticated search (requires CVE Program membership) * CVE JSON 5.1 format support * CVSS v3.1/v3.0/v2.0 metrics * CWE weakness mapping * Product/version configuration data * Authoritative reference links * Exploit indicator detection * Multi-container data (CNA + ADP) * * LIMITATIONS: * - Search functionality requires CVE Program membership and Secretariat role * - Public access limited to individual CVE ID lookups * - Rate limiting applies (respects API guidelines) * * NOTE: Without authentication, only public read-only access to individual CVE records * is available. All administrative, creation, and modification operations require * CVE Program membership with appropriate roles. */ export class MITRESourceImplementation extends BaseCVESourceImplementation { /** * Override base class authentication headers for MITRE's custom multi-header system. * * MITRE API requires three specific headers for CVE Program member authentication: * - CVE-API-ORG: Organization short name (from MITRE_API_ORG or CVE_API_ORG env var) * - CVE-API-USER: Username (from MITRE_API_USER or CVE_API_USER env var) * - CVE-API-KEY: API key (from MITRE_API_KEY env var, configured via apiKeyEnvVar) * * This authentication system is only required for search operations and CVE Program * member features. Individual CVE lookups work without authentication. * * @override Base class getAuthHeaders() method * @returns Record of MITRE-specific authentication headers */ getAuthHeaders() { const headers = {}; // Check if authentication is configured const apiKey = this.apiKey; const config = getAppConfiguration(); const apiOrg = config.security.apiKeys.mitreOrg; const apiUser = config.security.apiKeys.mitreUser; if (apiKey && apiOrg && apiUser) { headers['CVE-API-ORG'] = apiOrg; headers['CVE-API-USER'] = apiUser; headers['CVE-API-KEY'] = apiKey; logger.debug('MITRE CVE Program authentication headers configured'); } else { // Log which credentials are missing for debugging const missing = []; if (!apiKey) { missing.push('API key (MITRE_API_KEY)'); } if (!apiOrg) { missing.push('organization (MITRE_API_ORG)'); } if (!apiUser) { missing.push('username (MITRE_API_USER)'); } if (missing.length > 0) { logger.debug(`MITRE CVE Program authentication not configured (missing: ${missing.join(', ')}). Using public access only.`); } } return headers; } /** * Check if authenticated search endpoints can be used. * Requires CVE Program membership with proper credentials. */ hasAuthentication() { const apiKey = this.apiKey; const config = getAppConfiguration(); const apiOrg = config.security.apiKeys.mitreOrg; const apiUser = config.security.apiKeys.mitreUser; return !!(apiKey && apiOrg && apiUser); } buildSearchRequest(filters) { // If we have a specific CVE ID, redirect to details request if (filters.cveId) { return this.buildDetailsRequest(filters.cveId); } // Check if authenticated search is available if (this.hasAuthentication()) { // Use the authenticated /cve endpoint for search (requires Secretariat role) const url = new URL(`${this.getBaseUrl()}/cve`); const params = new URLSearchParams(); // Add search filters based on MITRE CVE Services API documentation if (filters.keyword) { // MITRE API doesn't have direct keyword search in the same way as NVD // Keywords would need to be handled through description or summary filters logger.warn('Direct keyword search not supported by MITRE CVE Services API'); } // Date range filters - MITRE uses ISO 8601 format if (filters.pubStartDate || filters.pubEndDate) { if (filters.pubStartDate) { params.append('time_published.gt', filters.pubStartDate); } if (filters.pubEndDate) { params.append('time_published.lt', filters.pubEndDate); } } if (filters.lastModStartDate || filters.lastModEndDate) { if (filters.lastModStartDate) { params.append('time_modified.gt', filters.lastModStartDate); } if (filters.lastModEndDate) { params.append('time_modified.lt', filters.lastModEndDate); } } // CVE state filter if (filters.vulnStatus) { params.append('state', filters.vulnStatus.toLowerCase()); } // Pagination - MITRE uses page-based pagination if (filters.startIndex !== undefined) { const page = Math.floor(filters.startIndex / (filters.resultsPerPage || 20)) + 1; params.append('page', page.toString()); } if (filters.resultsPerPage) { params.append('count_only', 'false'); // Note: MITRE API may have different parameter names for page size } url.search = params.toString(); return { url: url.toString(), options: this.getRequestOptions() }; } // For public access without authentication, search is not supported const errorMsg = 'MITRE CVE Services API search requires CVE Program membership with Secretariat role. ' + 'Configure MITRE authentication (MITRE_API_KEY, MITRE_API_ORG, MITRE_API_USER) or use specific CVE ID lookup instead.'; logger.warn(errorMsg); throw new Error(errorMsg); } buildDetailsRequest(cveId) { // MITRE CVE Services API format: GET /cve/{id} // Public endpoint - no authentication required for individual CVE lookups // Documentation: https://cveawg.mitre.org/api-docs/ const url = `${this.getBaseUrl()}/cve/${cveId}`; return { url, options: this.getRequestOptions() }; } normalizeSearchResults(data) { try { // Handle both single CVE response (details) and search results if (Array.isArray(data)) { // Authenticated search response - array of CVE records const cves = data.map(item => this.normalizeCVEData(item)).filter(Boolean); return { cves, totalResults: cves.length, startIndex: 0, resultsPerPage: cves.length, format: 'MITRE_CVE_JSON_5.1', version: '5.1', timestamp: new Date().toISOString(), }; } else if (data && typeof data === 'object') { // Single CVE response (from details request or single search result) const cve = this.normalizeCVEData(data); return { cves: cve ? [cve] : [], totalResults: cve ? 1 : 0, startIndex: 0, resultsPerPage: 1, format: 'MITRE_CVE_JSON_5.1', version: '5.1', timestamp: new Date().toISOString(), }; } else { logger.warn('Unexpected response format from MITRE API', { responseType: typeof data, isArray: Array.isArray(data), dataKeys: data && typeof data === 'object' ? Object.keys(data) : 'not an object', }); throw new Error('CVE not found in MITRE database: unexpected response format'); } } catch (error) { logger.error('Failed to normalize MITRE CVE data:', error instanceof Error ? error : new Error(String(error))); return { cves: [], totalResults: 0, startIndex: 0, resultsPerPage: 0, format: 'MITRE_CVE_JSON_5.1', version: '5.1', timestamp: new Date().toISOString(), }; } } normalizeCVEData(data) { const cveData = data; // MITRE API returns CVE JSON 5.1 format // Expected structure: { cveMetadata, containers: { cna, adp }, dataType, dataVersion } if (!cveData.cveMetadata) { logger.warn('Invalid MITRE CVE response: missing cveMetadata', { dataKeys: Object.keys(cveData || {}), hasContainers: !!cveData.containers, }); throw new Error('CVE not found in MITRE database: missing cveMetadata'); } const cveMetadata = cveData.cveMetadata; const containers = cveData.containers || {}; const cnaContainer = containers.cna || {}; const adpContainers = containers.adp || []; const cve = { id: cveMetadata.cveId || 'unknown', sourceIdentifier: cveMetadata.assignerOrgId || 'mitre.org', published: cveMetadata.datePublished || '', lastModified: cveMetadata.dateUpdated || cveMetadata.datePublished || '', vulnStatus: this.mapMITREState(cveMetadata.state), cveTags: [], descriptions: normalizeDescriptions(cnaContainer.descriptions || []), dataSource: { name: 'mitre', version: cveData.dataVersion || '5.1', lastUpdated: cveMetadata.dateUpdated || new Date().toISOString(), url: this.getBaseUrl(), }, processingInfo: { extractedAt: new Date().toISOString(), normalizedBy: 'mitre_source', rawDataAvailable: true, }, }; // Extract metrics from CNA container if (cnaContainer.metrics && Array.isArray(cnaContainer.metrics)) { cve.metrics = this.normalizeMITREMetrics(cnaContainer.metrics); } // Extract metrics from ADP containers adpContainers.forEach((adp) => { if (adp.metrics && Array.isArray(adp.metrics)) { const adpMetrics = this.normalizeMITREMetrics(adp.metrics); if (adpMetrics) { if (!cve.metrics) { cve.metrics = {}; } // Merge ADP metrics (they are additional data points) // Handle specific metric types safely if (adpMetrics.cvssMetricV31) { if (!cve.metrics.cvssMetricV31) { cve.metrics.cvssMetricV31 = []; } cve.metrics.cvssMetricV31.push(...adpMetrics.cvssMetricV31); } if (adpMetrics.cvssMetricV30) { if (!cve.metrics.cvssMetricV30) { cve.metrics.cvssMetricV30 = []; } cve.metrics.cvssMetricV30.push(...adpMetrics.cvssMetricV30); } if (adpMetrics.cvssMetricV2) { if (!cve.metrics.cvssMetricV2) { cve.metrics.cvssMetricV2 = []; } cve.metrics.cvssMetricV2.push(...adpMetrics.cvssMetricV2); } } } }); // Extract weaknesses (problem types) if (cnaContainer.problemTypes && Array.isArray(cnaContainer.problemTypes)) { cve.weaknesses = this.normalizeMITREWeaknesses(cnaContainer.problemTypes); } // Extract affected products/configurations if (cnaContainer.affected && Array.isArray(cnaContainer.affected)) { cve.configurations = this.normalizeMITREConfigurations(cnaContainer.affected); } // Extract references if (cnaContainer.references && Array.isArray(cnaContainer.references)) { cve.references = this.normalizeMITREReferences(cnaContainer.references); } // Calculate exploit indicators during normalization cve.exploitIndicators = this.calculateExploitIndicators(cve.references); return cve; } mapMITREState(state) { switch (state?.toUpperCase()) { case 'PUBLISHED': return 'PUBLISHED'; case 'REJECTED': return 'REJECTED'; case 'RESERVED': return 'RESERVED'; case 'DISPUTED': return 'DISPUTED'; default: return 'PUBLISHED'; } } normalizeMITREMetrics(metrics) { if (!metrics) { return undefined; } const result = {}; // Handle MITRE's CVSS format (may follow CVE JSON 5.0 structure) if (Array.isArray(metrics)) { metrics.forEach(metric => { const metricData = metric; if (metricData.cvssV3_1) { if (!result.cvssMetricV31) { result.cvssMetricV31 = []; } const cvss31 = metricData.cvssV3_1; result.cvssMetricV31.push({ source: 'mitre.org', type: 'Primary', cvssData: { version: '3.1', vectorString: String(cvss31.vectorString || ''), baseScore: Number(cvss31.baseScore || 0), baseSeverity: String(cvss31.baseSeverity || 'UNKNOWN'), attackVector: String(cvss31.attackVector || 'UNKNOWN'), attackComplexity: String(cvss31.attackComplexity || 'UNKNOWN'), privilegesRequired: String(cvss31.privilegesRequired || 'UNKNOWN'), userInteraction: String(cvss31.userInteraction || 'UNKNOWN'), scope: String(cvss31.scope || 'UNKNOWN'), confidentialityImpact: String(cvss31.confidentialityImpact || 'UNKNOWN'), integrityImpact: String(cvss31.integrityImpact || 'UNKNOWN'), availabilityImpact: String(cvss31.availabilityImpact || 'UNKNOWN'), }, }); } if (metricData.cvssV3_0) { if (!result.cvssMetricV30) { result.cvssMetricV30 = []; } const cvss30 = metricData.cvssV3_0; result.cvssMetricV30.push({ source: 'mitre.org', type: 'Primary', cvssData: { version: '3.0', vectorString: String(cvss30.vectorString || ''), baseScore: Number(cvss30.baseScore || 0), baseSeverity: String(cvss30.baseSeverity || 'UNKNOWN'), attackVector: String(cvss30.attackVector || 'UNKNOWN'), attackComplexity: String(cvss30.attackComplexity || 'UNKNOWN'), privilegesRequired: String(cvss30.privilegesRequired || 'UNKNOWN'), userInteraction: String(cvss30.userInteraction || 'UNKNOWN'), scope: String(cvss30.scope || 'UNKNOWN'), confidentialityImpact: String(cvss30.confidentialityImpact || 'UNKNOWN'), integrityImpact: String(cvss30.integrityImpact || 'UNKNOWN'), availabilityImpact: String(cvss30.availabilityImpact || 'UNKNOWN'), }, }); } if (metricData.cvssV2_0) { if (!result.cvssMetricV2) { result.cvssMetricV2 = []; } const cvss2 = metricData.cvssV2_0; result.cvssMetricV2.push({ source: 'mitre.org', type: 'Primary', cvssData: { version: '2.0', vectorString: String(cvss2.vectorString || ''), baseScore: Number(cvss2.baseScore || 0), accessVector: String(cvss2.accessVector || 'UNKNOWN'), accessComplexity: String(cvss2.accessComplexity || 'UNKNOWN'), authentication: String(cvss2.authentication || 'UNKNOWN'), confidentialityImpact: String(cvss2.confidentialityImpact || 'UNKNOWN'), integrityImpact: String(cvss2.integrityImpact || 'UNKNOWN'), availabilityImpact: String(cvss2.availabilityImpact || 'UNKNOWN'), }, }); } }); } return Object.keys(result).length > 0 ? result : undefined; } normalizeMITREWeaknesses(problemTypes) { if (!Array.isArray(problemTypes)) { return []; } const weaknesses = []; problemTypes.forEach(problemType => { if (Array.isArray(problemType.descriptions)) { weaknesses.push({ source: 'mitre.org', type: 'Primary', description: problemType.descriptions.map((desc) => ({ lang: desc.lang || 'en', value: desc.value || String(desc.description) || 'Unknown weakness', })), }); } }); return weaknesses; } normalizeMITREConfigurations(affected) { if (!Array.isArray(affected)) { return []; } return affected.map(item => ({ nodes: [{ operator: 'OR', negate: false, cpeMatch: Array.isArray(item.cpes) ? item.cpes.map((cpe) => ({ vulnerable: true, criteria: cpe, matchCriteriaId: '', })) : [], }], })); } normalizeMITREReferences(references) { if (!Array.isArray(references)) { return []; } return references.map(ref => ({ url: ref.url || String(ref.link) || '', source: 'mitre.org', tags: Array.isArray(ref.tags) ? ref.tags : Array.isArray(ref.categories) ? ref.categories : [], name: typeof ref.name === 'string' ? ref.name : typeof ref.title === 'string' ? ref.title : undefined, refsource: typeof ref.refsource === 'string' ? ref.refsource : typeof ref.source === 'string' ? ref.source : undefined, })); } formatDate(dateString) { // MITRE may expect ISO 8601 format try { const date = new Date(dateString); return date.toISOString(); } catch { return dateString; // Return as-is if parsing fails } } async testConnection() { try { // Test with a HEAD request to the base API const response = await fetch(this.getBaseUrl(), { method: 'HEAD', signal: AbortSignal.timeout(5000), }); return response.ok; } catch { return false; } } // MITRE-specific validation methods /** * Validates MITRE-specific configuration */ validateSourceSpecificConfig() { const errors = []; const warnings = []; // Validate MITRE-specific endpoints if (this.config.endpoints?.search) { const searchUrl = this.config.endpoints.search; if (!searchUrl.includes('mitre.org') && !searchUrl.includes('localhost')) { warnings.push(`MITRE source ${this.config.name}: search endpoint doesn't appear to be official MITRE API`); } } if (this.config.endpoints?.details) { const detailsUrl = this.config.endpoints.details; if (!detailsUrl.includes('mitre.org') && !detailsUrl.includes('localhost')) { warnings.push(`MITRE source ${this.config.name}: details endpoint doesn't appear to be official MITRE API`); } } // MITRE has multiple databases and formats if (this.config.enabled) { if (!this.config.endpoints?.details) { errors.push(`MITRE source ${this.config.name}: requires details endpoint for CVE lookups`); } // Check for supported formats const supportedFormats = ['json', 'xml']; if (this.config.features) { const hasFormat = supportedFormats.some(format => this.config.features && this.config.features.includes(format)); if (!hasFormat) { const formatMsg = `MITRE source ${this.config.name}: ` + 'no supported format specified (json, xml)'; warnings.push(formatMsg); } } } return { isValid: errors.length === 0, errors, warnings }; } /** * MITRE CVE Services API validation - checks both public and authenticated access */ validateSourceSpecificApiKey() { const warnings = []; // MITRE API key is optional - public access works without authentication if (this.config.apiKeyRequired && !this.apiKey) { warnings.push(`MITRE source ${this.config.name}: marked as requiring API key but none provided. Only public CVE lookups will be available.`); } // Check for complete authentication setup const config = getAppConfiguration(); const apiOrg = config.security.apiKeys.mitreOrg; const apiUser = config.security.apiKeys.mitreUser; if (this.apiKey && (!apiOrg || !apiUser)) { warnings.push(`MITRE source ${this.config.name}: API key provided but missing organization or username. Configure MITRE_API_ORG and MITRE_API_USER for full functionality.`); } if (!this.apiKey && (apiOrg || apiUser)) { warnings.push(`MITRE source ${this.config.name}: Organization or username configured but missing API key. Configure MITRE_API_KEY for authenticated access.`); } return { isValid: true, errors: [], warnings }; } /** * MITRE API key environment variable from configuration */ getApiKeyEnvironmentVariable() { return this.config.apiKeyEnvVar || 'MITRE_API_KEY'; } /** * Alternative environment variables for MITRE API key */ getAlternativeApiKeyEnvironmentVariables() { return ['CVE_API_KEY', 'MITRE_TOKEN', 'MITRE_ACCESS_KEY']; } /** * MITRE API key identifier for mapping */ getApiKeyIdentifier() { return 'mitre'; } /** * Check if this source matches MITRE-related identifiers */ matchesSourceIdentifier(identifier) { const lowerIdentifier = identifier.toLowerCase(); return lowerIdentifier === 'mitre' || lowerIdentifier === 'mitre.org' || lowerIdentifier === 'cve' || lowerIdentifier.includes('mitre') || super.matchesSourceIdentifier(identifier); } /** * MITRE API key validation */ canUseApiKey(apiKey) { if (!apiKey) { return false; } // Basic validation - MITRE CVE Services API keys vary in format // Typically they are alphanumeric strings of reasonable length return apiKey.length >= 10 && /^[a-zA-Z0-9\-_]+$/.test(apiKey); } getRequestOptions() { return { headers: { 'Accept': 'application/json', 'User-Agent': this.getUserAgent(), ...this.getAuthHeaders(), }, timeout: this.getTimeout(), }; } } //# sourceMappingURL=mitre.js.map