vulnzap-core
Version:
Secure AI-generated code by intercepting vulnerabilities in real-time
195 lines • 7.82 kB
JavaScript
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