mirror of
https://github.com/securego/gosec.git
synced 2024-12-26 12:35:52 +00:00
56f943b802
This feature adds support to generate auto fixes for Go scanning findings using LLM (AI). In a first instance, it relies on Gemini API to get a suggestion for a solution. This can be later extended, to integrate also other AI providers. --------- Signed-off-by: Cosmin Cojocar <ccojocar@google.com> Co-authored-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com> Co-authored-by: Cosmin Cojocar <ccojocar@google.com>
252 lines
6.7 KiB
Go
252 lines
6.7 KiB
Go
package sarif
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/securego/gosec/v2"
|
|
"github.com/securego/gosec/v2/cwe"
|
|
"github.com/securego/gosec/v2/issue"
|
|
)
|
|
|
|
// GenerateReport converts a gosec report into a SARIF report
|
|
func GenerateReport(rootPaths []string, data *gosec.ReportInfo) (*Report, error) {
|
|
rules := []*ReportingDescriptor{}
|
|
|
|
results := []*Result{}
|
|
cweTaxa := []*ReportingDescriptor{}
|
|
weaknesses := map[string]*cwe.Weakness{}
|
|
|
|
for _, issue := range data.Issues {
|
|
if issue.Cwe != nil {
|
|
_, ok := weaknesses[issue.Cwe.ID]
|
|
if !ok {
|
|
weakness := cwe.Get(issue.Cwe.ID)
|
|
weaknesses[issue.Cwe.ID] = weakness
|
|
cweTaxon := parseSarifTaxon(weakness)
|
|
cweTaxa = append(cweTaxa, cweTaxon)
|
|
}
|
|
}
|
|
|
|
rule := parseSarifRule(issue)
|
|
var ruleIndex int
|
|
rules, ruleIndex = addRuleInOrder(rules, rule)
|
|
|
|
location, err := parseSarifLocation(issue, rootPaths)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := NewResult(
|
|
issue.RuleID,
|
|
ruleIndex,
|
|
getSarifLevel(issue.Severity.String()),
|
|
issue.What,
|
|
buildSarifSuppressions(issue.Suppressions),
|
|
issue.Autofix,
|
|
).WithLocations(location)
|
|
|
|
results = append(results, result)
|
|
}
|
|
|
|
sort.SliceStable(cweTaxa, func(i, j int) bool { return cweTaxa[i].ID < cweTaxa[j].ID })
|
|
|
|
tool := NewTool(buildSarifDriver(rules, data.GosecVersion))
|
|
|
|
cweTaxonomy := buildCWETaxonomy(cweTaxa)
|
|
|
|
run := NewRun(tool).
|
|
WithTaxonomies(cweTaxonomy).
|
|
WithResults(results...)
|
|
|
|
return NewReport(Version, Schema).
|
|
WithRuns(run), nil
|
|
}
|
|
|
|
// addRuleInOrder inserts a rule into the rules slice keeping the rules IDs order, it returns the new rules
|
|
// slice and the position where the rule was inserted
|
|
func addRuleInOrder(rules []*ReportingDescriptor, rule *ReportingDescriptor) ([]*ReportingDescriptor, int) {
|
|
position := 0
|
|
for i, r := range rules {
|
|
if r.ID < rule.ID {
|
|
continue
|
|
}
|
|
if r.ID == rule.ID {
|
|
return rules, i
|
|
}
|
|
position = i
|
|
break
|
|
}
|
|
rules = append(rules, nil)
|
|
copy(rules[position+1:], rules[position:])
|
|
rules[position] = rule
|
|
return rules, position
|
|
}
|
|
|
|
// parseSarifRule return SARIF rule field struct
|
|
func parseSarifRule(i *issue.Issue) *ReportingDescriptor {
|
|
cwe := issue.GetCweByRule(i.RuleID)
|
|
name := i.RuleID
|
|
if cwe != nil {
|
|
name = cwe.Name
|
|
}
|
|
return &ReportingDescriptor{
|
|
ID: i.RuleID,
|
|
Name: name,
|
|
ShortDescription: NewMultiformatMessageString(i.What),
|
|
FullDescription: NewMultiformatMessageString(i.What),
|
|
Help: NewMultiformatMessageString(fmt.Sprintf("%s\nSeverity: %s\nConfidence: %s\n",
|
|
i.What, i.Severity.String(), i.Confidence.String())),
|
|
Properties: &PropertyBag{
|
|
"tags": []string{"security", i.Severity.String()},
|
|
"precision": strings.ToLower(i.Confidence.String()),
|
|
},
|
|
DefaultConfiguration: &ReportingConfiguration{
|
|
Level: getSarifLevel(i.Severity.String()),
|
|
},
|
|
Relationships: []*ReportingDescriptorRelationship{
|
|
buildSarifReportingDescriptorRelationship(i.Cwe),
|
|
},
|
|
}
|
|
}
|
|
|
|
func buildSarifReportingDescriptorRelationship(weakness *cwe.Weakness) *ReportingDescriptorRelationship {
|
|
if weakness == nil {
|
|
return nil
|
|
}
|
|
return &ReportingDescriptorRelationship{
|
|
Target: &ReportingDescriptorReference{
|
|
ID: weakness.ID,
|
|
GUID: uuid3(weakness.SprintID()),
|
|
ToolComponent: NewToolComponentReference(cwe.Acronym),
|
|
},
|
|
Kinds: []string{"superset"},
|
|
}
|
|
}
|
|
|
|
func buildCWETaxonomy(taxa []*ReportingDescriptor) *ToolComponent {
|
|
return NewToolComponent(cwe.Acronym, cwe.Version, cwe.InformationURI).
|
|
WithReleaseDateUtc(cwe.ReleaseDateUtc).
|
|
WithDownloadURI(cwe.DownloadURI).
|
|
WithOrganization(cwe.Organization).
|
|
WithShortDescription(NewMultiformatMessageString(cwe.Description)).
|
|
WithIsComprehensive(true).
|
|
WithLanguage("en").
|
|
WithMinimumRequiredLocalizedDataSemanticVersion(cwe.Version).
|
|
WithTaxa(taxa...)
|
|
}
|
|
|
|
func parseSarifTaxon(weakness *cwe.Weakness) *ReportingDescriptor {
|
|
return &ReportingDescriptor{
|
|
ID: weakness.ID,
|
|
GUID: uuid3(weakness.SprintID()),
|
|
HelpURI: weakness.SprintURL(),
|
|
FullDescription: NewMultiformatMessageString(weakness.Description),
|
|
ShortDescription: NewMultiformatMessageString(weakness.Name),
|
|
}
|
|
}
|
|
|
|
func parseSemanticVersion(version string) string {
|
|
if len(version) == 0 {
|
|
return "devel"
|
|
}
|
|
if strings.HasPrefix(version, "v") {
|
|
return version[1:]
|
|
}
|
|
return version
|
|
}
|
|
|
|
func buildSarifDriver(rules []*ReportingDescriptor, gosecVersion string) *ToolComponent {
|
|
semanticVersion := parseSemanticVersion(gosecVersion)
|
|
return NewToolComponent("gosec", gosecVersion, "https://github.com/securego/gosec/").
|
|
WithSemanticVersion(semanticVersion).
|
|
WithSupportedTaxonomies(NewToolComponentReference(cwe.Acronym)).
|
|
WithRules(rules...)
|
|
}
|
|
|
|
func uuid3(value string) string {
|
|
return uuid.NewMD5(uuid.Nil, []byte(value)).String()
|
|
}
|
|
|
|
// parseSarifLocation return SARIF location struct
|
|
func parseSarifLocation(i *issue.Issue, rootPaths []string) (*Location, error) {
|
|
region, err := parseSarifRegion(i)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
artifactLocation := parseSarifArtifactLocation(i, rootPaths)
|
|
return NewLocation(NewPhysicalLocation(artifactLocation, region)), nil
|
|
}
|
|
|
|
func parseSarifArtifactLocation(i *issue.Issue, rootPaths []string) *ArtifactLocation {
|
|
var filePath string
|
|
for _, rootPath := range rootPaths {
|
|
if strings.HasPrefix(i.File, rootPath) {
|
|
filePath = strings.Replace(i.File, rootPath+"/", "", 1)
|
|
}
|
|
}
|
|
return NewArtifactLocation(filePath)
|
|
}
|
|
|
|
func parseSarifRegion(i *issue.Issue) (*Region, error) {
|
|
lines := strings.Split(i.Line, "-")
|
|
startLine, err := strconv.Atoi(lines[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
endLine := startLine
|
|
if len(lines) > 1 {
|
|
endLine, err = strconv.Atoi(lines[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
col, err := strconv.Atoi(i.Col)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var code string
|
|
line := startLine
|
|
codeLines := strings.Split(i.Code, "\n")
|
|
for _, codeLine := range codeLines {
|
|
lineStart := fmt.Sprintf("%d:", line)
|
|
if strings.HasPrefix(codeLine, lineStart) {
|
|
code += strings.TrimSpace(
|
|
strings.TrimPrefix(codeLine, lineStart))
|
|
if endLine > startLine {
|
|
code += "\n"
|
|
}
|
|
line++
|
|
if line > endLine {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
snippet := NewArtifactContent(code)
|
|
return NewRegion(startLine, endLine, col, col, "go").WithSnippet(snippet), nil
|
|
}
|
|
|
|
func getSarifLevel(s string) Level {
|
|
switch s {
|
|
case "LOW":
|
|
return Warning
|
|
case "MEDIUM":
|
|
return Error
|
|
case "HIGH":
|
|
return Error
|
|
default:
|
|
return Note
|
|
}
|
|
}
|
|
|
|
func buildSarifSuppressions(suppressions []issue.SuppressionInfo) []*Suppression {
|
|
var sarifSuppressionList []*Suppression
|
|
for _, s := range suppressions {
|
|
sarifSuppressionList = append(sarifSuppressionList, NewSuppression(s.Kind, s.Justification))
|
|
}
|
|
return sarifSuppressionList
|
|
}
|