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
276 lines • 10.3 kB
JavaScript
import { createContextLogger } from '../utils/logger.js';
import { getExploitIndicators } from '../utils/validation.js';
import { VERSION, PACKAGE_NAME } from '../version.js';
const logger = createContextLogger('SourceBase');
// Abstract base class for all CVE source implementations
//
// Custom Authentication:
// To implement custom authentication (like MITRE's multi-header system):
// 1. Override the getAuthHeaders() method in your source implementation
// 2. Return appropriate headers based on your authentication scheme
// 3. Configuration fields like authHeaderType and authHeaderName are not needed
// when completely overriding the authentication method
//
// Standard Authentication Types (handled by base class):
// - 'apiKey': Simple API key in custom header
// - 'bearer': Bearer token in Authorization header
// - 'token': Token with 'token' prefix in Authorization header
// - 'none': No authentication
//
export class BaseCVESourceImplementation {
// Standard User-Agent for all CVE sources
static USER_AGENT = `${PACKAGE_NAME}/${VERSION}`;
config;
apiKey;
constructor(config, apiKey) {
this.config = config;
this.apiKey = apiKey;
}
// Validation methods - can be overridden by concrete implementations
/**
* Validates the source configuration
* Default implementation checks basic requirements
*/
validateConfig() {
const errors = [];
const warnings = [];
// Basic validation that applies to all sources
if (!this.config.baseUrl) {
errors.push(`Source ${this.config.name}: missing baseUrl`);
}
if (!this.config.endpoints) {
errors.push(`Source ${this.config.name}: missing endpoints`);
}
if (this.config.priority === undefined || this.config.priority < 0) {
errors.push(`Source ${this.config.name}: invalid priority`);
}
// Check if enabled source has required endpoints
if (this.config.enabled) {
if (!this.config.endpoints?.search && !this.config.endpoints?.details) {
warnings.push(`Source ${this.config.name}: enabled but missing both search and details endpoints`);
}
}
// Allow concrete implementations to add their own validation
const specificValidation = this.validateSourceSpecificConfig();
errors.push(...specificValidation.errors);
warnings.push(...specificValidation.warnings);
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validates API key configuration for this source
* Default implementation provides basic API key checks
*/
validateApiKey() {
const errors = [];
const warnings = [];
// Check if API key is required but missing
if (this.config.apiKeyRequired && !this.apiKey) {
warnings.push(`Source ${this.config.name}: requires API key but none configured (will use rate-limited access)`);
}
// Allow concrete implementations to add their own API key validation
const specificValidation = this.validateSourceSpecificApiKey();
errors.push(...specificValidation.errors);
warnings.push(...specificValidation.warnings);
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
/**
* Override this method in concrete implementations for source-specific configuration validation
*/
validateSourceSpecificConfig() {
return { isValid: true, errors: [], warnings: [] };
}
/**
* Override this method in concrete implementations for source-specific API key validation
*/
validateSourceSpecificApiKey() {
return { isValid: true, errors: [], warnings: [] };
}
/**
* Gets the expected API key environment variable name for this source
* Override in concrete implementations to specify the correct env var
*/
getApiKeyEnvironmentVariable() {
return undefined;
}
/**
* Gets alternative environment variable names that might contain the API key
* Override in concrete implementations to specify alternative env vars
*/
getAlternativeApiKeyEnvironmentVariables() {
return [];
}
/**
* Determines if this source can use the provided API key
* Override in concrete implementations for source-specific key validation
*/
canUseApiKey(apiKey) {
return !!apiKey;
}
/**
* Gets the source-specific identifier used for API key mapping
* Override in concrete implementations to specify the correct identifier
*/
getApiKeyIdentifier() {
return this.config.name.toLowerCase();
}
/**
* Determines if this source matches the given source name/identifier
* Used for API key resolution from external mappings
*/
matchesSourceIdentifier(identifier) {
const lowerIdentifier = identifier.toLowerCase();
const sourceName = this.config.name.toLowerCase();
// Default matching logic - override in concrete implementations for more specific matching
return lowerIdentifier === sourceName ||
lowerIdentifier === this.getApiKeyIdentifier();
}
/**
* Performs comprehensive validation of the source
*/
validateFull() {
const allErrors = [];
const allWarnings = [];
// Run all validation checks
const configValidation = this.validateConfig();
const apiKeyValidation = this.validateApiKey();
allErrors.push(...configValidation.errors, ...apiKeyValidation.errors);
allWarnings.push(...configValidation.warnings, ...apiKeyValidation.warnings);
return {
isValid: allErrors.length === 0,
errors: allErrors,
warnings: allWarnings,
};
}
// Default implementation for connection testing (can be overridden)
async testConnection() {
try {
const response = await fetch(this.getBaseUrl(), {
method: 'HEAD',
signal: AbortSignal.timeout(5000),
});
return response.ok;
}
catch {
return false;
}
}
// Helper methods available to all implementations
getBaseUrl() {
return this.config.baseUrl;
}
getTimeout() {
return this.config.requestTimeout || 5000;
}
/**
* Gets authentication headers for API requests.
* Override this method in concrete implementations for custom authentication schemes.
*
* @returns Record of header name to header value
*/
getAuthHeaders() {
const headers = {};
// No authentication required
if (!this.config.authHeaderType || this.config.authHeaderType === 'none') {
return headers;
}
// Standard single-header authentication types
if (!this.apiKey) {
logger.debug(`No API key available for ${this.config.name}`);
return headers;
}
if (!this.config.authHeaderName) {
const warnMsg = `Auth header name not specified for ${this.config.name} ` +
`with auth type ${this.config.authHeaderType}`;
logger.warn(warnMsg);
return headers;
}
switch (this.config.authHeaderType) {
case 'apiKey':
headers[this.config.authHeaderName] = this.apiKey;
break;
case 'bearer':
headers[this.config.authHeaderName] = `Bearer ${this.apiKey}`;
break;
case 'token':
headers[this.config.authHeaderName] = `token ${this.apiKey}`;
break;
default:
logger.warn(`Unknown auth header type: ${this.config.authHeaderType}`);
}
return headers;
}
/**
* Public method to get authentication headers for external use (e.g., SourceManager)
* This method delegates to the protected getAuthHeaders() method that can be overridden by subclasses
*
* @returns Record of header name to header value
*/
getRequestAuthHeaders() {
return this.getAuthHeaders();
}
/**
* Gets the standard User-Agent string for all CVE sources
*/
getUserAgent() {
return BaseCVESourceImplementation.USER_AGENT;
}
// Get source configuration
getConfig() {
return this.config;
}
// Configuration getters for convenience
getName() { return this.config.name; }
isEnabled() { return this.config.enabled; }
getPriority() { return this.config.priority; }
getFeatures() { return this.config.features || []; }
// Check if source supports a specific feature
supportsFeature(feature) {
return this.config.features?.includes(feature) || false;
}
// Alias for supportsFeature for backward compatibility
hasFeature(feature) {
return this.supportsFeature(feature);
}
/**
* Calculate exploit indicators for a CVE based on its references
* This method should be called during CVE normalization
*/
calculateExploitIndicators(references) {
if (!references || references.length === 0) {
return {
hasExploitIndicators: false,
indicators: [],
calculatedAt: new Date().toISOString(),
};
}
const indicators = [];
const exploitIndicators = getExploitIndicators();
for (const ref of references) {
for (const [source, pattern] of Object.entries(exploitIndicators)) {
if (pattern.test(ref.url)) {
indicators.push({
source,
type: source === 'metasploit' ? 'metasploit' : 'exploit',
url: ref.url,
title: `${source} reference`,
verified: false,
});
}
}
}
return {
hasExploitIndicators: indicators.length > 0,
indicators,
calculatedAt: new Date().toISOString(),
};
}
}
//# sourceMappingURL=base.js.map