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