Skip to content

Commit f8691bd

Browse files
authored
fix(sarif): avoid invalid null relationships in SARIF output (#1569)
* fix(sarif): avoid null relationships for rules without CWE * test(sarif): add offline schema validation and self-scan coverage * test(sarif): fix lint in self-scan helper
1 parent ade1d0e commit f8691bd

File tree

5 files changed

+3495
-24
lines changed

5 files changed

+3495
-24
lines changed

report/sarif/common_test.go

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ package sarif_test
33
import (
44
"bufio"
55
"bytes"
6+
_ "embed"
67
"encoding/json"
78
"fmt"
8-
"net/http"
99
"sync"
10-
"time"
1110

1211
. "github.com/onsi/ginkgo/v2"
1312
"github.com/santhosh-tekuri/jsonschema/v6"
@@ -16,30 +15,20 @@ import (
1615
)
1716

1817
var (
19-
sarifSchemaOnce sync.Once
20-
sarifSchema *jsonschema.Schema
21-
sarifSchemaErr error
22-
sarifSchemaClient = &http.Client{Timeout: 30 * time.Second}
18+
sarifSchemaOnce sync.Once
19+
sarifSchema *jsonschema.Schema
20+
sarifSchemaErr error
2321
)
2422

23+
//go:embed testdata/sarif-schema-2.1.0.json
24+
var sarifSchemaJSON []byte
25+
2526
func validateSarifSchema(report *sarif.Report) error {
2627
GinkgoHelper()
2728
sarifSchemaOnce.Do(func() {
28-
resp, err := sarifSchemaClient.Get(sarif.Schema)
29-
if err != nil {
30-
sarifSchemaErr = fmt.Errorf("fetch sarif schema: %w", err)
31-
return
32-
}
33-
defer resp.Body.Close()
34-
35-
if resp.StatusCode != http.StatusOK {
36-
sarifSchemaErr = fmt.Errorf("fetch sarif schema: unexpected status %s", resp.Status)
37-
return
38-
}
39-
40-
schema, err := jsonschema.UnmarshalJSON(resp.Body)
29+
schema, err := jsonschema.UnmarshalJSON(bytes.NewReader(sarifSchemaJSON))
4130
if err != nil {
42-
sarifSchemaErr = fmt.Errorf("error unmarshaling schema: %w", err)
31+
sarifSchemaErr = fmt.Errorf("unmarshal local sarif schema: %w", err)
4332
return
4433
}
4534

report/sarif/formatter.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ func parseSarifRule(i *issue.Issue) *ReportingDescriptor {
9494
if cwe != nil {
9595
name = cwe.Name
9696
}
97-
return &ReportingDescriptor{
97+
relationship := buildSarifReportingDescriptorRelationship(i.Cwe)
98+
rule := &ReportingDescriptor{
9899
ID: i.RuleID,
99100
Name: name,
100101
ShortDescription: NewMultiformatMessageString(i.What),
@@ -108,10 +109,11 @@ func parseSarifRule(i *issue.Issue) *ReportingDescriptor {
108109
DefaultConfiguration: &ReportingConfiguration{
109110
Level: getSarifLevel(i.Severity.String()),
110111
},
111-
Relationships: []*ReportingDescriptorRelationship{
112-
buildSarifReportingDescriptorRelationship(i.Cwe),
113-
},
114112
}
113+
if relationship != nil {
114+
rule.Relationships = []*ReportingDescriptorRelationship{relationship}
115+
}
116+
return rule
115117
}
116118

117119
func buildSarifReportingDescriptorRelationship(weakness *cwe.Weakness) *ReportingDescriptorRelationship {

report/sarif/sarif_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,38 @@ var _ = Describe("Sarif Formatter", func() {
6262
Expect(validateSarifSchema(sarifReport)).To(Succeed())
6363
})
6464

65+
It("sarif formatted report should not include null relationships when CWE is missing (issue #1568)", func() {
66+
issueWithoutCWE := []*issue.Issue{
67+
{
68+
File: "/home/src/project/test.go",
69+
Line: "1",
70+
Col: "1",
71+
RuleID: "G706",
72+
What: "Log injection via taint analysis",
73+
Confidence: issue.High,
74+
Severity: issue.Low,
75+
Code: "1: testcode",
76+
Cwe: nil,
77+
},
78+
}
79+
80+
reportInfo := gosec.NewReportInfo(issueWithoutCWE, &gosec.Metrics{}, map[string][]gosec.Error{}).WithVersion("v2.24.0")
81+
82+
sarifReport, err := sarif.GenerateReport([]string{}, reportInfo)
83+
Expect(err).ShouldNot(HaveOccurred())
84+
Expect(validateSarifSchema(sarifReport)).To(Succeed())
85+
86+
Expect(sarifReport.Runs[0].Tool.Driver.Rules).To(HaveLen(1))
87+
Expect(sarifReport.Runs[0].Tool.Driver.Rules[0].Relationships).To(BeNil())
88+
89+
buf := new(bytes.Buffer)
90+
err = sarif.WriteReport(buf, reportInfo, []string{})
91+
Expect(err).ShouldNot(HaveOccurred())
92+
output := buf.String()
93+
Expect(output).NotTo(ContainSubstring(`"relationships":[null]`))
94+
Expect(output).NotTo(ContainSubstring(`"relationships": [null]`))
95+
})
96+
6597
It("sarif formatted report should not include fixes when autofix is empty (issue #1482)", func() {
6698
ruleID := "G304"
6799
cwe := issue.GetCweByRule(ruleID)

report/sarif/self_scan_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package sarif_test
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"log"
7+
"path/filepath"
8+
"runtime"
9+
10+
. "github.com/onsi/ginkgo/v2"
11+
. "github.com/onsi/gomega"
12+
13+
"github.com/securego/gosec/v2"
14+
"github.com/securego/gosec/v2/report/sarif"
15+
"github.com/securego/gosec/v2/rules"
16+
)
17+
18+
var _ = Describe("Sarif Self Scan", func() {
19+
It("produces locally valid sarif without null relationships when scanning gosec source", func() {
20+
repoRoot := currentRepoRoot()
21+
22+
config := gosec.NewConfig()
23+
logger := log.New(io.Discard, "", 0)
24+
analyzer := gosec.NewAnalyzer(config, false, true, false, 4, logger)
25+
26+
ruleList := rules.Generate(false, rules.NewRuleFilter(false, "G401"))
27+
analyzer.LoadRules(ruleList.RulesInfo())
28+
29+
excludedDirs := gosec.ExcludedDirsRegExp([]string{"vendor", ".git"})
30+
packagePaths, err := gosec.PackagePaths(filepath.Join(repoRoot, "..."), excludedDirs)
31+
Expect(err).ShouldNot(HaveOccurred())
32+
Expect(packagePaths).ShouldNot(BeEmpty())
33+
34+
err = analyzer.Process(nil, packagePaths...)
35+
Expect(err).ShouldNot(HaveOccurred())
36+
37+
issues, metrics, errors := analyzer.Report()
38+
Expect(issues).ShouldNot(BeEmpty())
39+
reportInfo := gosec.NewReportInfo(issues, metrics, errors).WithVersion("test")
40+
41+
sarifReport, err := sarif.GenerateReport([]string{repoRoot}, reportInfo)
42+
Expect(err).ShouldNot(HaveOccurred())
43+
44+
Expect(validateSarifSchema(sarifReport)).To(Succeed())
45+
46+
encoded, err := json.Marshal(sarifReport)
47+
Expect(err).ShouldNot(HaveOccurred())
48+
Expect(encoded).NotTo(ContainSubstring(`"relationships":[null]`))
49+
Expect(encoded).NotTo(ContainSubstring(`"relationships": [null]`))
50+
})
51+
})
52+
53+
func currentRepoRoot() string {
54+
programCounter, currentFile, line, ok := runtime.Caller(0)
55+
if !ok || programCounter == 0 || line <= 0 {
56+
return filepath.Clean(".")
57+
}
58+
return filepath.Clean(filepath.Join(filepath.Dir(currentFile), "..", ".."))
59+
}

0 commit comments

Comments
 (0)