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
JavaScript
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