mirror of
https://github.com/imjasonh/govulncheck-action
synced 2026-07-18 22:56:04 +00:00
Initial implementation of govulncheck GitHub Action
This action runs govulncheck on Go projects and creates GitHub annotations for vulnerabilities found in dependencies and code paths. Features: - Automated vulnerability scanning with govulncheck - Smart annotations on go.mod for vulnerable dependencies - Annotations on source code lines that call vulnerable functions - Configurable working directory - Output variables for vulnerability detection The implementation includes: - Modular architecture with separate concerns (execution, parsing, annotation) - Comprehensive test suite with 100% function coverage - Example vulnerable Go module for demonstration - GitHub workflow example - Full documentation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
39e329ab61
19 changed files with 5314 additions and 0 deletions
87
lib/annotator.js
Normal file
87
lib/annotator.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
class AnnotationCreator {
|
||||
constructor(core, fsModule = fs) {
|
||||
this.core = core;
|
||||
this.fs = fsModule;
|
||||
}
|
||||
|
||||
async createAnnotations(vulnerabilities, parser, workingDirectory = '.') {
|
||||
const modules = parser.extractUniqueModules(vulnerabilities);
|
||||
const callSites = parser.extractCallSites(vulnerabilities);
|
||||
|
||||
// Annotate go.mod for vulnerable modules
|
||||
await this.annotateGoMod(modules, vulnerabilities, workingDirectory);
|
||||
|
||||
// Annotate source files for call sites
|
||||
this.annotateCallSites(callSites);
|
||||
}
|
||||
|
||||
async annotateGoMod(modules, vulnerabilities, workingDirectory) {
|
||||
if (modules.length === 0) return;
|
||||
|
||||
const goModPath = path.join(workingDirectory, 'go.mod');
|
||||
let goModContent = '';
|
||||
|
||||
try {
|
||||
goModContent = await this.fs.readFile(goModPath, 'utf8');
|
||||
} catch (error) {
|
||||
this.core.warning(`Could not read go.mod: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const goModLines = goModContent.split('\n');
|
||||
|
||||
for (const module of modules) {
|
||||
// Find the vulnerability info for this module
|
||||
const vuln = vulnerabilities.find(v =>
|
||||
v.finding.trace &&
|
||||
v.finding.trace[0] &&
|
||||
v.finding.trace[0].module === module
|
||||
);
|
||||
|
||||
// Find line in go.mod
|
||||
for (let i = 0; i < goModLines.length; i++) {
|
||||
if (goModLines[i].includes(module)) {
|
||||
const annotation = {
|
||||
path: 'go.mod',
|
||||
start_line: i + 1,
|
||||
end_line: i + 1,
|
||||
annotation_level: 'warning',
|
||||
message: `Vulnerable module: ${module}`,
|
||||
title: 'Security Vulnerability'
|
||||
};
|
||||
|
||||
if (vuln && vuln.finding.osv) {
|
||||
annotation.message += ` (${vuln.finding.osv})`;
|
||||
}
|
||||
|
||||
this.core.warning(annotation.message, annotation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
annotateCallSites(callSites) {
|
||||
for (const site of callSites) {
|
||||
const annotation = {
|
||||
path: site.filename,
|
||||
start_line: site.line,
|
||||
end_line: site.line,
|
||||
annotation_level: 'warning',
|
||||
message: `Vulnerable code path: ${site.function}`,
|
||||
title: 'Security Vulnerability'
|
||||
};
|
||||
|
||||
if (site.osv) {
|
||||
annotation.message += ` (${site.osv})`;
|
||||
}
|
||||
|
||||
this.core.warning(annotation.message, annotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnnotationCreator;
|
||||
38
lib/govulncheck.js
Normal file
38
lib/govulncheck.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
const exec = require('@actions/exec');
|
||||
|
||||
class GovulncheckRunner {
|
||||
constructor(execModule = exec) {
|
||||
this.exec = execModule;
|
||||
}
|
||||
|
||||
async install() {
|
||||
await this.exec.exec('go', ['install', 'golang.org/x/vuln/cmd/govulncheck@latest']);
|
||||
}
|
||||
|
||||
async run(workingDirectory = './...') {
|
||||
let output = '';
|
||||
let errorOutput = '';
|
||||
|
||||
const options = {
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
output += data.toString();
|
||||
},
|
||||
stderr: (data) => {
|
||||
errorOutput += data.toString();
|
||||
}
|
||||
},
|
||||
ignoreReturnCode: true
|
||||
};
|
||||
|
||||
const exitCode = await this.exec.exec('govulncheck', ['-json', workingDirectory], options);
|
||||
|
||||
return {
|
||||
output,
|
||||
errorOutput,
|
||||
exitCode
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GovulncheckRunner;
|
||||
58
lib/parser.js
Normal file
58
lib/parser.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
class VulnerabilityParser {
|
||||
parse(output) {
|
||||
const vulnerabilities = [];
|
||||
const lines = output.split('\n').filter(line => line.trim());
|
||||
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const json = JSON.parse(line);
|
||||
|
||||
// Check for vulnerability findings
|
||||
if (json.finding) {
|
||||
vulnerabilities.push(json);
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip non-JSON lines
|
||||
}
|
||||
}
|
||||
|
||||
return vulnerabilities;
|
||||
}
|
||||
|
||||
extractUniqueModules(vulnerabilities) {
|
||||
const modules = new Set();
|
||||
|
||||
for (const vuln of vulnerabilities) {
|
||||
const finding = vuln.finding;
|
||||
if (finding.trace && finding.trace.length > 0 && finding.trace[0].module) {
|
||||
modules.add(finding.trace[0].module);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(modules);
|
||||
}
|
||||
|
||||
extractCallSites(vulnerabilities) {
|
||||
const callSites = [];
|
||||
|
||||
for (const vuln of vulnerabilities) {
|
||||
const finding = vuln.finding;
|
||||
if (finding.trace) {
|
||||
for (const frame of finding.trace) {
|
||||
if (frame.position && frame.position.filename) {
|
||||
callSites.push({
|
||||
filename: frame.position.filename,
|
||||
line: frame.position.line || 1,
|
||||
function: frame.function || 'unknown function',
|
||||
osv: finding.osv || null
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return callSites;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VulnerabilityParser;
|
||||
Loading…
Add table
Add a link
Reference in a new issue