1
0
Fork 0
mirror of https://github.com/imjasonh/govulncheck-action synced 2026-07-09 17:18:14 +00:00
govulncheck-action/lib/summary.js
Jason Hall c4a3cd53b0 build: migrate to ESM and esbuild
Updates the project to use ESM modules and replaces ncc with esbuild. This fixes build failures caused by newer @actions/core versions which use strict ESM exports that ncc cannot resolve.

Also updates tests to use ESM and dependency injection.

Signed-off-by: Jason Hall <imjasonh@gmail.com>
2026-02-21 02:54:49 -05:00

186 lines
5.9 KiB
JavaScript

class SummaryGenerator {
constructor(core) {
this.core = core;
}
async generateSummary(vulnerabilities, parser, workingDirectory = '.') {
const modules = parser.extractUniqueModules(vulnerabilities);
const callSites = parser.extractCallSites(vulnerabilities);
// Group vulnerabilities by module
const vulnsByModule = new Map();
for (const vuln of vulnerabilities) {
if (vuln.finding.trace && vuln.finding.trace[0]) {
const module = vuln.finding.trace[0].module;
if (!vulnsByModule.has(module)) {
vulnsByModule.set(module, []);
}
vulnsByModule.get(module).push(vuln);
}
}
// Sort modules for consistent output
const sortedModules = Array.from(vulnsByModule.keys()).sort();
// Start building the summary
await this.core.summary.addHeading('🔍 Govulncheck Security Report', 1).addEOL();
if (vulnerabilities.length === 0) {
await this.core.summary.addRaw('✅ No vulnerabilities found!').addEOL().write();
return;
}
// Add overview
await this.core.summary
.addHeading('📊 Overview', 2)
.addList([
`Total vulnerabilities found: ${vulnerabilities.length}`,
`Vulnerable modules: ${modules.length}`,
`Vulnerable code locations: ${callSites.length}`,
])
.addEOL();
// Add vulnerable modules section
await this.core.summary.addHeading('📦 Vulnerable Modules', 2).addEOL();
for (const module of sortedModules) {
const moduleVulns = vulnsByModule.get(module);
const moduleInfo = moduleVulns[0].finding.trace[0];
await this.core.summary
.addHeading(`${module}`, 3)
.addRaw(`Current version: ${moduleInfo.version}`)
.addEOL()
.addEOL();
// Sort vulnerabilities by OSV ID
const sortedVulns = moduleVulns.sort((a, b) => {
const aId = a.finding.osv || '';
const bId = b.finding.osv || '';
return aId.localeCompare(bId);
});
// Create a table of vulnerabilities
const tableRows = [['Vulnerability', 'Summary', 'Fixed Version', 'Details']];
for (const vuln of sortedVulns) {
const osvId = vuln.finding.osv;
const summary = vuln.osvDetails?.summary || 'No summary available';
const fixedVersion = vuln.finding.fixed_version || 'Not specified';
let details = [];
if (vuln.osvDetails?.aliases) {
const cves = vuln.osvDetails.aliases.filter((a) => a.startsWith('CVE-'));
if (cves.length > 0) {
details.push(`CVE: ${cves.join(', ')}`);
}
}
// For the link, we'll add it as a separate line after the table
tableRows.push([
osvId,
summary.length > 60 ? summary.substring(0, 60) + '...' : summary,
fixedVersion,
details.join(', '),
]);
}
await this.core.summary.addTable(tableRows);
// Add links separately since we can't put them in the table
await this.core.summary.addEOL().addRaw('Links: ');
for (let i = 0; i < sortedVulns.length; i++) {
const vuln = sortedVulns[i];
if (i > 0) await this.core.summary.addRaw(' | ');
await this.core.summary.addLink(
vuln.finding.osv,
`https://pkg.go.dev/vuln/${vuln.finding.osv}`
);
}
await this.core.summary.addEOL().addEOL();
}
// Add vulnerable code locations section
if (callSites.length > 0) {
await this.core.summary.addHeading('🚨 Vulnerable Code Locations', 2).addEOL();
// Group call sites by file
const callSitesByFile = new Map();
for (const site of callSites) {
const filePath =
workingDirectory === '.' ? site.filename : `${workingDirectory}/${site.filename}`;
if (!callSitesByFile.has(filePath)) {
callSitesByFile.set(filePath, []);
}
callSitesByFile.get(filePath).push(site);
}
// Sort files
const sortedFiles = Array.from(callSitesByFile.keys()).sort();
for (const file of sortedFiles) {
await this.core.summary.addHeading(`📄 ${file}`, 3);
const sites = callSitesByFile.get(file);
for (const site of sites) {
await this.core.summary.addRaw(`• Line ${site.line}: calls ${site.vulnerableFunction}`);
if (site.osv) {
await this.core.summary
.addRaw(' - ')
.addLink(site.osv, `https://pkg.go.dev/vuln/${site.osv}`);
}
await this.core.summary.addEOL();
}
await this.core.summary.addEOL();
}
}
// Add recommendations section
await this.core.summary.addHeading('💡 Recommendations', 2).addEOL();
const recommendations = [];
// Find modules that need updates
for (const module of sortedModules) {
const moduleVulns = vulnsByModule.get(module);
const fixedVersions = moduleVulns
.map((v) => v.finding.fixed_version)
.filter((v) => v)
.sort();
if (fixedVersions.length > 0) {
// Get the latest fixed version
const latestFix = fixedVersions[fixedVersions.length - 1];
recommendations.push(`Update ${module} to version ${latestFix} or later`);
}
}
if (recommendations.length > 0) {
await this.core.summary.addList(recommendations);
} else {
await this.core.summary.addRaw('No specific version recommendations available.');
}
await this.core.summary
.addEOL()
.addSeparator()
.addEOL()
.addRaw('🔗 Learn more about these vulnerabilities:')
.addEOL()
.addRaw('• ')
.addLink('Go Vulnerability Database', 'https://pkg.go.dev/vuln/')
.addEOL()
.addRaw('• ')
.addLink('Govulncheck Documentation', 'https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck')
.addEOL();
// Write the summary
await this.core.summary.write();
}
}
export default SummaryGenerator;