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

747 lines 35 kB
import { BaseCVESourceImplementation } from './base.js'; import { createContextLogger } from '../utils/logger.js'; import { normalizeDescriptions } from '../utils/cve-utils.js'; const logger = createContextLogger('NVDSource'); // NVD Source Implementation export class NVDSourceImplementation extends BaseCVESourceImplementation { buildSearchRequest(filters) { // Validate NVD-specific parameters const validation = this.validateNVDSearchParameters(filters); if (validation.errors.length > 0) { throw new Error(`NVD API parameter validation failed: ${validation.errors.join('; ')}`); } // Log warnings if any if (validation.warnings.length > 0) { logger.warn(`NVD API parameter warnings: ${validation.warnings.join('; ')}`); } const params = new URLSearchParams(); // NVD API 2.0 parameters if (filters.keyword) { params.append('keywordSearch', filters.keyword); } if (filters.cveId) { params.append('cveId', filters.cveId); } // Map cpeNameId to virtualMatchString (NVD API 2.0 parameter name) if (filters.cpeNameId) { params.append('virtualMatchString', filters.cpeNameId); } // CVSS severity filtering if (filters.cvssV3Severity) { params.append('cvssV3Severity', filters.cvssV3Severity); } if (filters.cvssV2Severity) { params.append('cvssV2Severity', filters.cvssV2Severity); } if (filters.cvssV4Severity) { params.append('cvssV4Severity', filters.cvssV4Severity); } // CVSS metrics filtering if (filters.cvssV3Metrics) { params.append('cvssV3Metrics', filters.cvssV3Metrics); } if (filters.cvssV2Metrics) { params.append('cvssV2Metrics', filters.cvssV2Metrics); } if (filters.cvssV4Metrics) { params.append('cvssV4Metrics', filters.cvssV4Metrics); } // CWE filtering if (filters.cweId) { params.append('cweId', filters.cweId); } // CVE tag filtering if (filters.cveTag) { params.append('cveTag', filters.cveTag); } // Date filtering if (filters.pubStartDate) { params.append('pubStartDate', this.formatDate(filters.pubStartDate)); } if (filters.pubEndDate) { params.append('pubEndDate', this.formatDate(filters.pubEndDate)); } if (filters.lastModStartDate) { params.append('lastModStartDate', this.formatDate(filters.lastModStartDate)); } if (filters.lastModEndDate) { params.append('lastModEndDate', this.formatDate(filters.lastModEndDate)); } // Pagination if (filters.resultsPerPage) { params.append('resultsPerPage', Math.min(filters.resultsPerPage, 2000).toString()); } if (filters.startIndex) { params.append('startIndex', filters.startIndex.toString()); } // Boolean flags (NVD-specific) - these parameters should have no value const booleanFlags = []; if (filters.hasCertAlerts) { booleanFlags.push('hasCertAlerts'); } if (filters.hasCertNotes) { booleanFlags.push('hasCertNotes'); } if (filters.hasKev) { booleanFlags.push('hasKev'); } if (filters.hasOval) { booleanFlags.push('hasOval'); } if (filters.noRejected) { booleanFlags.push('noRejected'); } // CPE vulnerability filtering if (filters.isVulnerable && filters.cpeNameId) { booleanFlags.push('isVulnerable'); } // Keyword exact match if (filters.keywordExactMatch && filters.keyword) { booleanFlags.push('keywordExactMatch'); } // Virtual match string (alternative to cpeName) if (filters.virtualMatchString) { params.append('virtualMatchString', filters.virtualMatchString); } // Default to 20 results if not specified if (!filters.resultsPerPage) { params.append('resultsPerPage', '20'); } // Construct the URL with boolean flags properly formatted let url = `${this.getBaseUrl()}?${params.toString()}`; if (booleanFlags.length > 0) { // Add boolean flags without values (no "=" sign) const separator = url.includes('?') && url.length > url.indexOf('?') + 1 ? '&' : ''; url += separator + booleanFlags.join('&'); } logger.debug('Built NVD search request', { url: url.length > 200 ? `${url.substring(0, 200)}...` : url, parameterCount: params.toString().split('&').length, booleanFlags: booleanFlags.length > 0 ? booleanFlags : undefined, hasApiKey: !!this.apiKey, }); return { url, options: this.getRequestOptions() }; } buildDetailsRequest(cveId) { const params = new URLSearchParams(); params.append('cveId', cveId); const url = `${this.getBaseUrl()}?${params.toString()}`; logger.debug('Built NVD details request', { cveId, url, hasApiKey: !!this.apiKey, }); return { url, options: this.getRequestOptions() }; } normalizeSearchResults(data) { // Type-safe conversion to NVD API response const nvdResponse = data; // Validate the response structure matches expected NVD API format this.validateNVDApiResponse(nvdResponse); // Handle NVD API 2.0 response structure // Handle case where vulnerabilities might be null or undefined const vulnerabilities = nvdResponse.vulnerabilities || []; // Defensive check: ensure vulnerabilities is an array (validation already checked this) if (!Array.isArray(vulnerabilities)) { throw new TypeError('Invalid NVD response: vulnerabilities must be an array'); } // Type-safe processing of vulnerability wrappers const cves = vulnerabilities.map((vuln) => this.normalizeCVEData(vuln.cve)); logger.debug('Normalized NVD search results', { vulnerabilityCount: vulnerabilities.length, totalResults: nvdResponse.totalResults || 0, startIndex: nvdResponse.startIndex || 0, resultsPerPage: nvdResponse.resultsPerPage || 20, format: nvdResponse.format, version: nvdResponse.version, }); return { cves, totalResults: nvdResponse.totalResults || 0, startIndex: nvdResponse.startIndex || 0, resultsPerPage: nvdResponse.resultsPerPage || 20, format: nvdResponse.format || 'NVD_CVE', version: nvdResponse.version || '2.0', timestamp: nvdResponse.timestamp || new Date().toISOString(), }; } normalizeCVEData(data) { let cveData = data; // Handle case where we receive full NVD API response for details requests // NVD API returns: { vulnerabilities: [{ cve: { ... } }] } if (cveData.vulnerabilities && Array.isArray(cveData.vulnerabilities)) { if (cveData.vulnerabilities.length === 0) { logger.warn('CVE not found in NVD database'); // Throw a specific error that indicates the CVE was not found // This allows the service to try other sources throw new Error('CVE not found in NVD database'); } // Type-safe handling of details response const detailsResponse = data; this.validateNVDApiResponse(detailsResponse); if (detailsResponse.vulnerabilities && detailsResponse.vulnerabilities.length > 0) { const vulnWrapper = detailsResponse.vulnerabilities[0]; cveData = vulnWrapper.cve; } } const normalizedReferences = this.normalizeNVDReferences(cveData.references); // Log warnings for missing or default data if (cveData.id === 'unknown' || !cveData.id) { logger.warn('CVE data missing ID, using fallback value', { sourceIdentifier: cveData.sourceIdentifier, published: cveData.published, }); } if (!cveData.descriptions || (Array.isArray(cveData.descriptions) && cveData.descriptions.length === 0)) { logger.warn('CVE data missing descriptions', { cveId: cveData.id || 'unknown' }); } if (!cveData.metrics) { logger.debug('CVE data has no CVSS metrics', { cveId: cveData.id || 'unknown' }); } return { id: cveData.id || 'unknown', sourceIdentifier: cveData.sourceIdentifier || 'nvd.nist.gov', published: cveData.published || new Date().toISOString(), lastModified: cveData.lastModified || new Date().toISOString(), vulnStatus: cveData.vulnStatus || 'PUBLISHED', cveTags: cveData.cveTags || [], descriptions: normalizeDescriptions(cveData.descriptions), metrics: this.normalizeNVDMetrics(cveData.metrics), weaknesses: cveData.weaknesses || [], configurations: cveData.configurations || [], references: normalizedReferences, // Calculate exploit indicators during normalization exploitIndicators: this.calculateExploitIndicators(normalizedReferences), // Data provenance dataSource: { name: 'nvd', version: '2.0', lastUpdated: new Date().toISOString(), url: this.getBaseUrl(), }, // Processing metadata processingInfo: { extractedAt: new Date().toISOString(), normalizedBy: 'nvd-source-implementation', rawDataAvailable: true, }, }; } normalizeNVDMetrics(metrics) { if (!metrics) { return undefined; } const result = {}; const processedVersions = []; // Handle CVSS v3.1 metrics if (metrics.cvssMetricV31) { processedVersions.push('v3.1'); result.cvssMetricV31 = metrics.cvssMetricV31.map((metric) => ({ source: metric.source || 'nvd.nist.gov', type: metric.type || 'Primary', cvssData: { version: '3.1', vectorString: metric.cvssData?.vectorString || '', baseScore: metric.cvssData?.baseScore || 0, baseSeverity: metric.cvssData?.baseSeverity || 'UNKNOWN', attackVector: metric.cvssData?.attackVector || 'UNKNOWN', attackComplexity: metric.cvssData?.attackComplexity || 'UNKNOWN', privilegesRequired: metric.cvssData?.privilegesRequired || 'UNKNOWN', userInteraction: metric.cvssData?.userInteraction || 'UNKNOWN', scope: metric.cvssData?.scope || 'UNKNOWN', confidentialityImpact: metric.cvssData?.confidentialityImpact || 'UNKNOWN', integrityImpact: metric.cvssData?.integrityImpact || 'UNKNOWN', availabilityImpact: metric.cvssData?.availabilityImpact || 'UNKNOWN', // Enhanced temporal and environmental metrics temporalScore: metric.cvssData?.temporalScore, temporalSeverity: metric.cvssData?.temporalSeverity, environmentalScore: metric.cvssData?.environmentalScore, environmentalSeverity: metric.cvssData?.environmentalSeverity, }, exploitabilityScore: metric.exploitabilityScore, impactScore: metric.impactScore, })); } // Handle CVSS v3.0 metrics if (metrics.cvssMetricV30) { processedVersions.push('v3.0'); result.cvssMetricV30 = metrics.cvssMetricV30.map((metric) => ({ source: metric.source || 'nvd.nist.gov', type: metric.type || 'Primary', cvssData: { version: '3.0', vectorString: metric.cvssData?.vectorString || '', baseScore: metric.cvssData?.baseScore || 0, baseSeverity: metric.cvssData?.baseSeverity || 'UNKNOWN', attackVector: metric.cvssData?.attackVector || 'UNKNOWN', attackComplexity: metric.cvssData?.attackComplexity || 'UNKNOWN', privilegesRequired: metric.cvssData?.privilegesRequired || 'UNKNOWN', userInteraction: metric.cvssData?.userInteraction || 'UNKNOWN', scope: metric.cvssData?.scope || 'UNKNOWN', confidentialityImpact: metric.cvssData?.confidentialityImpact || 'UNKNOWN', integrityImpact: metric.cvssData?.integrityImpact || 'UNKNOWN', availabilityImpact: metric.cvssData?.availabilityImpact || 'UNKNOWN', // Enhanced temporal and environmental metrics temporalScore: metric.cvssData?.temporalScore, temporalSeverity: metric.cvssData?.temporalSeverity, environmentalScore: metric.cvssData?.environmentalScore, environmentalSeverity: metric.cvssData?.environmentalSeverity, }, exploitabilityScore: metric.exploitabilityScore, impactScore: metric.impactScore, })); } // Handle CVSS v2 metrics if (metrics.cvssMetricV2) { processedVersions.push('v2.0'); result.cvssMetricV2 = metrics.cvssMetricV2.map((metric) => ({ source: metric.source || 'nvd.nist.gov', type: metric.type || 'Primary', cvssData: { version: '2.0', vectorString: metric.cvssData?.vectorString || '', baseScore: metric.cvssData?.baseScore || 0, baseSeverity: metric.cvssData?.baseSeverity, accessVector: metric.cvssData?.accessVector || 'UNKNOWN', accessComplexity: metric.cvssData?.accessComplexity || 'UNKNOWN', authentication: metric.cvssData?.authentication || 'UNKNOWN', confidentialityImpact: metric.cvssData?.confidentialityImpact || 'UNKNOWN', integrityImpact: metric.cvssData?.integrityImpact || 'UNKNOWN', availabilityImpact: metric.cvssData?.availabilityImpact || 'UNKNOWN', // Enhanced temporal and environmental metrics temporalScore: metric.cvssData?.temporalScore, temporalSeverity: metric.cvssData?.temporalSeverity, environmentalScore: metric.cvssData?.environmentalScore, environmentalSeverity: metric.cvssData?.environmentalSeverity, }, exploitabilityScore: metric.exploitabilityScore, impactScore: metric.impactScore, })); } // Handle CVSS v4 metrics (newest version) if (metrics.cvssMetricV4) { processedVersions.push('v4.0'); result.cvssMetricV4 = metrics.cvssMetricV4.map((metric) => ({ source: metric.source || 'nvd.nist.gov', type: metric.type || 'Primary', cvssData: { version: '4.0', vectorString: metric.cvssData?.vectorString || '', baseScore: metric.cvssData?.baseScore || 0, baseSeverity: metric.cvssData?.baseSeverity || 'UNKNOWN', // CVSS v4 specific metrics attackVector: metric.cvssData?.attackVector || 'UNKNOWN', attackComplexity: metric.cvssData?.attackComplexity || 'UNKNOWN', attackRequirements: metric.cvssData?.attackRequirements || 'UNKNOWN', privilegesRequired: metric.cvssData?.privilegesRequired || 'UNKNOWN', userInteraction: metric.cvssData?.userInteraction || 'UNKNOWN', // Vulnerable system impact metrics vulnConfidentialityImpact: metric.cvssData?.vulnConfidentialityImpact || metric.cvssData?.vulnerableSystemConfidentiality || 'UNKNOWN', vulnIntegrityImpact: metric.cvssData?.vulnIntegrityImpact || metric.cvssData?.vulnerableSystemIntegrity || 'UNKNOWN', vulnAvailabilityImpact: metric.cvssData?.vulnAvailabilityImpact || metric.cvssData?.vulnerableSystemAvailability || 'UNKNOWN', // Subsequent system impact metrics subConfidentialityImpact: metric.cvssData?.subConfidentialityImpact || metric.cvssData?.subsequentSystemConfidentiality || 'UNKNOWN', subIntegrityImpact: metric.cvssData?.subIntegrityImpact || metric.cvssData?.subsequentSystemIntegrity || 'UNKNOWN', subAvailabilityImpact: metric.cvssData?.subAvailabilityImpact || metric.cvssData?.subsequentSystemAvailability || 'UNKNOWN', // Enhanced temporal and environmental metrics temporalScore: metric.cvssData?.temporalScore, temporalSeverity: metric.cvssData?.temporalSeverity, environmentalScore: metric.cvssData?.environmentalScore, environmentalSeverity: metric.cvssData?.environmentalSeverity, }, exploitabilityScore: metric.exploitabilityScore, impactScore: metric.impactScore, })); } // Log metrics processing info if (processedVersions.length > 0) { logger.debug('Processed CVSS metrics', { versions: processedVersions, totalVersions: processedVersions.length, }); } return Object.keys(result).length > 0 ? result : undefined; } normalizeNVDReferences(references) { if (!references || !Array.isArray(references)) { if (references !== undefined && references !== null) { logger.warn('Invalid references data structure', { type: typeof references, isArray: Array.isArray(references), }); } return []; } return references.map((ref) => ({ url: ref.url || '', source: ref.source, tags: ref.tags || [], // Enhanced reference fields name: ref.name || ref.title, refsource: ref.refsource, })); } formatDate(dateString) { // NVD expects ISO 8601 format: YYYY-MM-DDTHH:MM:SS.sss{UTC offset} try { const date = new Date(dateString); const formatted = date.toISOString(); // Validate the parsed date is reasonable if (isNaN(date.getTime())) { logger.warn('Invalid date provided for NVD API', { originalDate: dateString }); return dateString; } return formatted; } catch (error) { logger.warn('Failed to format date for NVD API', { originalDate: dateString, error: error.message, }); return dateString; // Return as-is if parsing fails } } async testConnection() { logger.debug('Testing NVD API connection'); try { // Test with a simple query const testUrl = `${this.getBaseUrl()}?resultsPerPage=1`; const response = await fetch(testUrl, { ...this.getRequestOptions(), signal: AbortSignal.timeout(5000), }); const success = response.ok; logger.debug('NVD connection test completed', { success, status: response.status, statusText: response.statusText, }); return success; } catch (error) { logger.warn('NVD connection test failed', { error: error.message, timeout: '5000ms', }); return false; } } /** * Enhanced connection test that respects rate limits */ async testConnectionWithRateLimit() { logger.debug('Testing NVD API connection with rate limit monitoring'); try { // Build a minimal test request const testUrl = `${this.getBaseUrl()}?resultsPerPage=1`; const response = await fetch(testUrl, { ...this.getRequestOptions(), signal: AbortSignal.timeout(20000), }); // Log rate limit headers if available const remaining = response.headers.get('X-RateLimit-Remaining'); const resetTime = response.headers.get('X-RateLimit-Reset'); if (remaining && resetTime) { logger.info(`NVD Rate Limit: ${remaining} requests remaining, resets at ${resetTime}`); } const success = response.ok; logger.debug('NVD connection test with rate limit completed', { success, status: response.status, statusText: response.statusText, rateLimit: { remaining, resetTime, }, }); return success; } catch (error) { logger.warn(`NVD connection test failed: ${error.message}`); return false; } } // NVD-specific validation methods /** * Validates NVD-specific configuration */ validateSourceSpecificConfig() { const errors = []; const warnings = []; logger.debug('Validating NVD-specific configuration', { sourceName: this.config.name, enabled: this.config.enabled, hasSearchEndpoint: !!this.config.endpoints?.search, hasDetailsEndpoint: !!this.config.endpoints?.details, }); // Validate NVD-specific endpoints if (this.config.endpoints?.search) { const searchUrl = this.config.endpoints.search; if (!searchUrl.includes('nvd.nist.gov') && !searchUrl.includes('localhost')) { warnings.push(`NVD source ${this.config.name}: search endpoint doesn't appear to be official NVD API`); } } if (this.config.endpoints?.details) { const detailsUrl = this.config.endpoints.details; if (!detailsUrl.includes('nvd.nist.gov') && !detailsUrl.includes('localhost')) { warnings.push(`NVD source ${this.config.name}: details endpoint doesn't appear to be official NVD API`); } } // Validate required endpoints for NVD if (this.config.enabled) { if (!this.config.endpoints?.search && !this.config.endpoints?.details) { errors.push(`NVD source ${this.config.name}: must have either search or details endpoint configured`); } } // Check for deprecated configurations if (this.config.authHeaderType && this.config.authHeaderType !== 'apiKey') { const authTypeMsg = `NVD source ${this.config.name}: NVD API uses 'apiKey' auth type, ` + `found '${this.config.authHeaderType}'`; warnings.push(authTypeMsg); } if (this.config.authHeaderName && this.config.authHeaderName !== 'apiKey') { const headerNameMsg = `NVD source ${this.config.name}: NVD API uses 'apiKey' header name, ` + `found '${this.config.authHeaderName}'`; warnings.push(headerNameMsg); } const result = { isValid: errors.length === 0, errors, warnings }; logger.debug('NVD configuration validation completed', { isValid: result.isValid, errorCount: errors.length, warningCount: warnings.length, }); return result; } /** * Validates NVD API key format and configuration */ validateSourceSpecificApiKey() { const errors = []; const warnings = []; if (this.apiKey) { // NVD API keys should be UUIDs (36 characters with hyphens) const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; if (!uuidPattern.test(this.apiKey)) { warnings.push(`NVD source ${this.config.name}: API key format doesn't match expected UUID pattern`); } if (this.apiKey.length < 10) { warnings.push(`NVD source ${this.config.name}: API key appears to be too short`); } } return { isValid: errors.length === 0, errors, warnings }; } /** * NVD-specific API key environment variable from configuration */ getApiKeyEnvironmentVariable() { return this.config.apiKeyEnvVar || 'NVD_API_KEY'; } /** * Alternative environment variables for NVD API key */ getAlternativeApiKeyEnvironmentVariables() { return ['CVE_API_KEY']; // Deprecated but sometimes used } /** * NVD API key identifier for mapping */ getApiKeyIdentifier() { return 'nvd'; } /** * Check if this source matches NVD-related identifiers */ matchesSourceIdentifier(identifier) { const lowerIdentifier = identifier.toLowerCase(); return lowerIdentifier === 'nvd' || lowerIdentifier === 'national' || lowerIdentifier.includes('nvd') || lowerIdentifier.includes('national') || super.matchesSourceIdentifier(identifier); } /** * Validate if the API key can be used by NVD */ canUseApiKey(apiKey) { if (!apiKey) { return false; } // NVD API keys should be UUIDs const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; return uuidPattern.test(apiKey) && apiKey.length >= 10; } /** * Validates NVD-specific search parameters */ validateNVDSearchParameters(filters) { const errors = []; const warnings = []; // NVD API requires BOTH start and end dates for date filtering if (filters.pubStartDate && !filters.pubEndDate) { errors.push('NVD API requires both pubStartDate and pubEndDate when filtering by publication date'); } if (!filters.pubStartDate && filters.pubEndDate) { errors.push('NVD API requires both pubStartDate and pubEndDate when filtering by publication date'); } if (filters.lastModStartDate && !filters.lastModEndDate) { errors.push('NVD API requires both lastModStartDate and lastModEndDate when filtering by last modified date'); } if (!filters.lastModStartDate && filters.lastModEndDate) { errors.push('NVD API requires both lastModStartDate and lastModEndDate when filtering by last modified date'); } // Check for conflicting CVSS version parameters const cvssVersions = []; if (filters.cvssV2Severity || filters.cvssV2Metrics) { cvssVersions.push('v2'); } if (filters.cvssV3Severity || filters.cvssV3Metrics) { cvssVersions.push('v3'); } if (filters.cvssV4Severity || filters.cvssV4Metrics) { cvssVersions.push('v4'); } if (cvssVersions.length > 1) { const versionErrorMsg = 'NVD API does not support mixing CVSS versions in a single request. ' + `Found: ${cvssVersions.join(', ')}`; errors.push(versionErrorMsg); } // Validate date range limitations (NVD allows max 120 days) if (filters.pubStartDate && filters.pubEndDate) { const startDate = new Date(filters.pubStartDate); const endDate = new Date(filters.pubEndDate); const daysDiff = Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)); if (daysDiff > 120) { errors.push(`NVD API allows maximum 120-day range for publication dates. Requested range: ${daysDiff} days`); } } if (filters.lastModStartDate && filters.lastModEndDate) { const startDate = new Date(filters.lastModStartDate); const endDate = new Date(filters.lastModEndDate); const daysDiff = Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)); if (daysDiff > 120) { errors.push(`NVD API allows maximum 120-day range for last modified dates. Requested range: ${daysDiff} days`); } } // Validate CPE vulnerability filtering requirement if (filters.isVulnerable && !filters.cpeNameId) { errors.push('isVulnerable parameter requires cpeNameId to be specified'); } // Validate keyword exact match requirement if (filters.keywordExactMatch && !filters.keyword) { errors.push('keywordExactMatch parameter requires keyword to be specified'); } // Validate virtual match string exclusions (both cpeNameId and virtualMatchString map to virtualMatchString parameter) if ((filters.virtualMatchString || filters.cpeNameId) && filters.isVulnerable) { errors.push('virtualMatchString/cpeNameId cannot be used with isVulnerable parameter'); } // Validate CVE tag values if (filters.cveTag) { const validTags = ['disputed', 'unsupported-when-assigned', 'exclusively-hosted-service']; if (!validTags.includes(filters.cveTag)) { errors.push(`Invalid CVE tag: ${filters.cveTag}. Valid tags: ${validTags.join(', ')}`); } } // Validate CWE ID format if (filters.cweId) { const cweId = filters.cweId; if (!cweId.match(/^(CWE-\d+|NVD-CWE-Other|NVD-CWE-noinfo)$/)) { const cweWarningMsg = `CWE ID format may be invalid: ${cweId}. ` + 'Expected format: CWE-123, NVD-CWE-Other, or NVD-CWE-noinfo'; warnings.push(cweWarningMsg); } } // Validate results per page limit if (filters.resultsPerPage && filters.resultsPerPage > 2000) { warnings.push(`NVD API maximum resultsPerPage is 2000, requested: ${filters.resultsPerPage}`); } return { errors, warnings }; } /** * Override base class authentication for NVD-specific API key header */ getAuthHeaders() { const headers = {}; if (this.apiKey) { headers['apiKey'] = this.apiKey; logger.debug('NVD authentication headers configured'); } return headers; } getRequestOptions() { return { headers: { 'Accept': 'application/json', 'User-Agent': this.getUserAgent(), ...this.getAuthHeaders(), }, timeout: this.getTimeout(), }; } /** * Validates NVD API response structure to detect API changes early */ validateNVDApiResponse(response) { // Check for required top-level properties that indicate a valid NVD response if (response === null || typeof response !== 'object') { throw new TypeError('NVD API response must be an object'); } // Check vulnerabilities array structure (optional field) if (response.vulnerabilities !== undefined) { if (response.vulnerabilities === null) { // NVD API sometimes returns null instead of empty array logger.warn('NVD API returned null for vulnerabilities - this may indicate an API change'); } else if (!Array.isArray(response.vulnerabilities)) { throw new TypeError('NVD API response vulnerabilities must be an array'); } else { // Validate structure of vulnerability wrappers for (const vuln of response.vulnerabilities) { if (!vuln || typeof vuln !== 'object') { throw new TypeError('Each vulnerability in NVD response must be an object'); } if (!vuln.cve || typeof vuln.cve !== 'object') { throw new TypeError('Each vulnerability wrapper must contain a cve object'); } } } } // Validate metadata fields have expected types (all optional) if (response.totalResults !== undefined && typeof response.totalResults !== 'number') { throw new TypeError('NVD API response totalResults must be a number'); } if (response.startIndex !== undefined && typeof response.startIndex !== 'number') { throw new TypeError('NVD API response startIndex must be a number'); } if (response.resultsPerPage !== undefined && typeof response.resultsPerPage !== 'number') { throw new TypeError('NVD API response resultsPerPage must be a number'); } if (response.format !== undefined && typeof response.format !== 'string') { throw new TypeError('NVD API response format must be a string'); } if (response.version !== undefined && typeof response.version !== 'string') { throw new TypeError('NVD API response version must be a string'); } if (response.timestamp !== undefined && typeof response.timestamp !== 'string') { throw new TypeError('NVD API response timestamp must be a string'); } // Log warnings for unexpected format changes if (response.format && response.format !== 'NVD_CVE') { const formatMsg = `NVD API response format changed from 'NVD_CVE' to '${response.format}' ` + '- this may indicate an API update'; logger.warn(formatMsg); } if (response.version && !response.version.startsWith('2.')) { const versionMsg = `NVD API response version changed from '2.x' to '${response.version}' ` + '- this may indicate an API update'; logger.warn(versionMsg); } } } //# sourceMappingURL=nvd.js.map