UNPKG

vulnzap-core

Version:

Secure AI-generated code by intercepting vulnerabilities in real-time

195 lines 7.82 kB
import fs from 'fs/promises'; import path from 'path'; import CONFIG from './serviceConfig.js'; export default class GitHubAdvisorySource { constructor() { this.CACHE_TTL = 24 * 60 * 60; // 24 hours in seconds this.isInitialized = false; this.cachePath = path.join(CONFIG.DATA_PATHS.CACHE_DIR, 'github-advisories'); this.initialize(); } async initialize() { if (this.isInitialized) return true; try { // Create cache directory if it doesn't exist await fs.mkdir(this.cachePath, { recursive: true }); this.isInitialized = true; return true; } catch (error) { console.error('Failed to initialize GitHub Advisory Source:', error); return false; } } /** * Save data to cache file * @private */ async _saveToCache(key, data, ttl = this.CACHE_TTL) { try { const cacheFile = path.join(this.cachePath, `${key.replace(/[^a-zA-Z0-9]/g, '_')}.json`); const cacheData = { data, expires: Date.now() + (ttl * 1000), created: Date.now() }; await fs.writeFile(cacheFile, JSON.stringify(cacheData, null, 2)); } catch (error) { console.error('Error saving to cache:', error); } } /** * Get data from cache * @private */ async _getFromCache(key) { try { const cacheFile = path.join(this.cachePath, `${key.replace(/[^a-zA-Z0-9]/g, '_')}.json`); const data = JSON.parse(await fs.readFile(cacheFile, 'utf8')); // Check if cache has expired if (data.expires < Date.now()) { await fs.unlink(cacheFile); return null; } return data.data; } catch (error) { return null; } } /** * Clear cache for specific key or all cache * @private */ async _clearCache(key) { try { if (key) { const cacheFile = path.join(this.cachePath, `${key.replace(/[^a-zA-Z0-9]/g, '_')}.json`); await fs.unlink(cacheFile).catch(() => { }); } else { const files = await fs.readdir(this.cachePath); await Promise.all(files.map(file => fs.unlink(path.join(this.cachePath, file)).catch(err => console.error(`Failed to delete cache file ${file}:`, err)))); } } catch (error) { console.error('Error clearing cache:', error); } } async makeApiRequest(endpoint = '') { try { const response = await fetch(`${CONFIG.SERVICE_ENDPOINTS.GITHUB_ADVISORY}`, { headers: { 'Accept': 'application/vnd.github+json', 'Authorization': `Bearer ${CONFIG.API_KEYS.GITHUB}`, 'X-GitHub-Api-Version': '2022-11-28' } }); if (!response.ok) { throw new Error(`GitHub API error: ${response.statusText}`); } const data = await response.json(); return data; } catch (error) { console.error('Error making GitHub API request:', error); throw error; } } async getAllAdvisoriesForEcosystem(ecosystem) { const cacheKey = `github:advisories:${ecosystem}`; // Try to get from cache first const cached = await this._getFromCache(cacheKey); if (cached) { return cached; } // Fetch all advisories const advisories = await this.makeApiRequest(); // Filter advisories for the specified ecosystem const ecosystemAdvisories = advisories.filter((advisory) => advisory.vulnerabilities.some(vuln => vuln.package.ecosystem.toLowerCase() === ecosystem.toLowerCase())); // Cache the results await this._saveToCache(cacheKey, ecosystemAdvisories, this.CACHE_TTL); return ecosystemAdvisories; } processAdvisoryResults(results, packageName, version, ecosystem) { const advisories = results.map(advisory => ({ id: advisory.ghsa_id, title: advisory.summary, severity: advisory.severity.toLowerCase(), cve_id: advisory.cve_id || undefined, description: advisory.description, })); // Extract fixed versions from the first advisory const fixedVersions = []; if (results[0].vulnerabilities && results[0].vulnerabilities.length > 0) { results[0].vulnerabilities.forEach(vuln => { if (vuln.first_patched_version) { fixedVersions.push(vuln.first_patched_version); } }); } return { isVulnerable: true, advisories, fixedVersions: fixedVersions.length > 0 ? fixedVersions : undefined, sources: ['github'] }; } async findVulnerabilities(packageName, version, ecosystem, options = {}) { try { if (!this.isInitialized) { await this.initialize(); } const cacheKey = `github:${ecosystem}:${packageName}:${version}`; if (!options.refresh) { const cached = await this._getFromCache(cacheKey); if (cached) { return cached; } } else { // Clear existing cache if refresh is requested await this._clearCache(cacheKey); } const existingAdvisories = await this.getAllAdvisoriesForEcosystem(ecosystem); const advisories = existingAdvisories.filter((advisory) => advisory.vulnerabilities.some(vuln => vuln.package.ecosystem.toLowerCase() === ecosystem.toLowerCase() && vuln.package.name.toLowerCase() === packageName.toLowerCase())); if (advisories.length === 0) { const result = { isVulnerable: false, message: 'No vulnerabilities found', sources: ['github'] }; await this._saveToCache(cacheKey, result, this.CACHE_TTL); return result; } const newAdvisories = await this.makeApiRequest(); // Filter advisories for the specified ecosystem and package const relevantAdvisories = newAdvisories.filter((advisory) => advisory.vulnerabilities.some(vuln => vuln.package.ecosystem.toLowerCase() === ecosystem.toLowerCase() && vuln.package.name.toLowerCase() === packageName.toLowerCase())); if (relevantAdvisories.length === 0) { const result = { isVulnerable: false, message: 'No vulnerabilities found', sources: ['github'] }; await this._saveToCache(cacheKey, result, this.CACHE_TTL); return result; } const vulnerabilityResult = this.processAdvisoryResults(relevantAdvisories, packageName, version, ecosystem); await this._saveToCache(cacheKey, vulnerabilityResult, this.CACHE_TTL); return vulnerabilityResult; } catch (error) { console.error(`Error finding GitHub vulnerabilities for ${packageName}@${version}:`, error); return { isVulnerable: false, error: `Error querying GitHub API: ${error.message || 'Unknown error'}`, sources: ['github'] }; } } } //# sourceMappingURL=github-advisory-source.js.map