package sarif import ( "fmt" "github.com/google/uuid" "runtime/debug" "strconv" "strings" "github.com/securego/gosec/v2" "github.com/securego/gosec/v2/cwe" "github.com/securego/gosec/v2/report/core" ) //GenerateReport Convert a gosec report to a Sarif Report func GenerateReport(rootPaths []string, data *core.ReportInfo) (*Report, error) { type rule struct { index int rule *ReportingDescriptor } rules := make([]*ReportingDescriptor, 0) rulesIndices := make(map[string]rule) lastRuleIndex := -1 results := []*Result{} cweTaxa := make([]*ReportingDescriptor, 0) weaknesses := make(map[string]*cwe.Weakness) for _, issue := range data.Issues { _, 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) } r, ok := rulesIndices[issue.RuleID] if !ok { lastRuleIndex++ r = rule{index: lastRuleIndex, rule: parseSarifRule(issue)} rulesIndices[issue.RuleID] = r rules = append(rules, r.rule) } location, err := parseSarifLocation(issue, rootPaths) if err != nil { return nil, err } result := NewResult(r.rule.ID, r.index, getSarifLevel(issue.Severity.String()), issue.What). WithLocations(location) results = append(results, result) } tool := NewTool(buildSarifDriver(rules)) cweTaxonomy := buildCWETaxonomy(cweTaxa) run := NewRun(tool). WithTaxonomies(cweTaxonomy). WithResults(results...) return NewReport(Version, Schema). WithRuns(run), nil } // parseSarifRule return SARIF rule field struct func parseSarifRule(issue *gosec.Issue) *ReportingDescriptor { return &ReportingDescriptor{ ID: issue.RuleID, Name: issue.What, ShortDescription: NewMultiformatMessageString(issue.What), FullDescription: NewMultiformatMessageString(issue.What), Help: NewMultiformatMessageString(fmt.Sprintf("%s\nSeverity: %s\nConfidence: %s\n", issue.What, issue.Severity.String(), issue.Confidence.String())), Properties: &PropertyBag{ "tags": []string{"security", issue.Severity.String()}, "precision": strings.ToLower(issue.Confidence.String()), }, DefaultConfiguration: &ReportingConfiguration{ Level: getSarifLevel(issue.Severity.String()), }, Relationships: []*ReportingDescriptorRelationship{ buildSarifReportingDescriptorRelationship(issue.Cwe), }, } } func buildSarifReportingDescriptorRelationship(weakness *cwe.Weakness) *ReportingDescriptorRelationship { 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). WithMinimumRequiredLocalizedDataSemanticVersion(cwe.Version). WithTaxa(taxa...) } func parseSarifTaxon(weakness *cwe.Weakness) *ReportingDescriptor { return &ReportingDescriptor{ ID: weakness.ID, Name: weakness.Name, GUID: uuid3(weakness.SprintID()), HelpURI: weakness.SprintURL(), ShortDescription: NewMultiformatMessageString(weakness.Description), } } func buildSarifDriver(rules []*ReportingDescriptor) *ToolComponent { buildInfo, ok := debug.ReadBuildInfo() var gosecVersion string if ok { gosecVersion = buildInfo.Main.Version[1:] } else { gosecVersion = "devel" } return NewToolComponent("gosec", gosecVersion, "https://github.com/securego/gosec/"). 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(issue *gosec.Issue, rootPaths []string) (*Location, error) { region, err := parseSarifRegion(issue) if err != nil { return nil, err } artifactLocation := parseSarifArtifactLocation(issue, rootPaths) return NewLocation(NewPhysicalLocation(artifactLocation, region)), nil } func parseSarifArtifactLocation(issue *gosec.Issue, rootPaths []string) *ArtifactLocation { var filePath string for _, rootPath := range rootPaths { if strings.HasPrefix(issue.File, rootPath) { filePath = strings.Replace(issue.File, rootPath+"/", "", 1) } } return NewArtifactLocation(filePath) } func parseSarifRegion(issue *gosec.Issue) (*Region, error) { lines := strings.Split(issue.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(issue.Col) if err != nil { return nil, err } snippet := NewArtifactContent(issue.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 } }