1
0
Fork 0
mirror of https://github.com/imjasonh/govulncheck-action synced 2026-07-08 16:56:01 +00:00
govulncheck-action/lib/summary.js
Jason Hall 3a117d24f8
Add vulnerable example and enhance action features
This commit adds a comprehensive example demonstrating the govulncheck action's capabilities, along with several enhancements to improve the user experience.

## Changes

### Example Setup
- Added example/main.go that uses golang.org/x/net v0.0.0-20220906165146-f3363e06e74c (vulnerable version)
- Example intentionally calls html.Parse to trigger vulnerability detection
- Demonstrates how the action creates annotations on vulnerable code

### Enhanced Annotations
- Fixed file path handling when running in subdirectories (e.g., './example')
- Added rich context to annotations including:
  - Vulnerability summaries and CVE numbers
  - Direct links to Go vulnerability database (pkg.go.dev/vuln)
  - Fixed version information
  - Clear indication of which function is vulnerable (e.g., "html.Parse" not "main")
- Sorted vulnerabilities by OSV ID for consistent display

### Suggested Fixes
- When vulnerable code is actually called, creates notice annotations on go.mod
- Shows current vs suggested dependency versions
- Lists which specific vulnerabilities have active call sites
- Provides actionable upgrade recommendations

### Workflow Summary
- Added comprehensive workflow summary using GitHub Actions summary API
- Displays vulnerabilities in formatted tables by module
- Shows vulnerable code locations organized by file
- Includes links to Go vulnerability database entries
- Provides clear recommendations for fixing vulnerabilities

### Bug Fixes
- Fixed JSON parsing to handle both JSON lines and multi-line JSON formats
- Fixed annotation file paths to be relative to repository root
- Improved vulnerability function name extraction from trace data
- Enhanced OSV detail parsing and storage

## Testing
The example workflow demonstrates all features by intentionally using a vulnerable dependency. When run, it will:
1. Detect vulnerabilities in golang.org/x/net
2. Create warning annotations on go.mod and main.go
3. Suggest specific version updates
4. Generate a detailed workflow summary

This provides a complete demonstration of the action's vulnerability detection and reporting capabilities.
2025-06-07 00:58:11 -04:00

197 lines
No EOL
6.1 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();
}
}
module.exports = SummaryGenerator;