1package output
2
3import (
4	"fmt"
5	"github.com/securego/gosec/v2"
6	"strconv"
7	"strings"
8)
9
10type sarifLevel string
11
12const (
13	sarifNone    = sarifLevel("none")
14	sarifNote    = sarifLevel("note")
15	sarifWarning = sarifLevel("warning")
16	sarifError   = sarifLevel("error")
17)
18
19type sarifProperties struct {
20	Tags []string `json:"tags"`
21}
22
23type sarifRule struct {
24	ID                   string              `json:"id"`
25	Name                 string              `json:"name"`
26	ShortDescription     *sarifMessage       `json:"shortDescription"`
27	FullDescription      *sarifMessage       `json:"fullDescription"`
28	Help                 *sarifMessage       `json:"help"`
29	Properties           *sarifProperties    `json:"properties"`
30	DefaultConfiguration *sarifConfiguration `json:"defaultConfiguration"`
31}
32
33type sarifConfiguration struct {
34	Level sarifLevel `json:"level"`
35}
36
37type sarifArtifactLocation struct {
38	URI string `json:"uri"`
39}
40
41type sarifRegion struct {
42	StartLine   uint64 `json:"startLine"`
43	EndLine     uint64 `json:"endLine"`
44	StartColumn uint64 `json:"startColumn"`
45	EndColumn   uint64 `json:"endColumn"`
46}
47
48type sarifPhysicalLocation struct {
49	ArtifactLocation *sarifArtifactLocation `json:"artifactLocation"`
50	Region           *sarifRegion           `json:"region"`
51}
52
53type sarifLocation struct {
54	PhysicalLocation *sarifPhysicalLocation `json:"physicalLocation"`
55}
56
57type sarifMessage struct {
58	Text string `json:"text"`
59}
60
61type sarifResult struct {
62	RuleID    string           `json:"ruleId"`
63	RuleIndex int              `json:"ruleIndex"`
64	Level     sarifLevel       `json:"level"`
65	Message   *sarifMessage    `json:"message"`
66	Locations []*sarifLocation `json:"locations"`
67}
68
69type sarifDriver struct {
70	Name           string       `json:"name"`
71	InformationURI string       `json:"informationUri"`
72	Rules          []*sarifRule `json:"rules,omitempty"`
73}
74
75type sarifTool struct {
76	Driver *sarifDriver `json:"driver"`
77}
78
79type sarifRun struct {
80	Tool    *sarifTool     `json:"tool"`
81	Results []*sarifResult `json:"results"`
82}
83
84type sarifReport struct {
85	Schema  string      `json:"$schema"`
86	Version string      `json:"version"`
87	Runs    []*sarifRun `json:"runs"`
88}
89
90// buildSarifReport return SARIF report struct
91func buildSarifReport() *sarifReport {
92	return &sarifReport{
93		Version: "2.1.0",
94		Schema:  "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.4.json",
95		Runs:    []*sarifRun{},
96	}
97}
98
99// buildSarifRule return SARIF rule field struct
100func buildSarifRule(issue *gosec.Issue) *sarifRule {
101	return &sarifRule{
102		ID:   fmt.Sprintf("%s (CWE-%s)", issue.RuleID, issue.Cwe.ID),
103		Name: issue.What,
104		ShortDescription: &sarifMessage{
105			Text: issue.What,
106		},
107		FullDescription: &sarifMessage{
108			Text: issue.What,
109		},
110		Help: &sarifMessage{
111			Text: fmt.Sprintf("%s\nSeverity: %s\nConfidence: %s\nCWE: %s", issue.What, issue.Severity.String(), issue.Confidence.String(), issue.Cwe.URL),
112		},
113		Properties: &sarifProperties{
114			Tags: []string{fmt.Sprintf("CWE-%s", issue.Cwe.ID), issue.Severity.String()},
115		},
116		DefaultConfiguration: &sarifConfiguration{
117			Level: getSarifLevel(issue.Severity.String()),
118		},
119	}
120}
121
122// buildSarifLocation return SARIF location struct
123func buildSarifLocation(issue *gosec.Issue, rootPaths []string) (*sarifLocation, error) {
124	var filePath string
125
126	lines := strings.Split(issue.Line, "-")
127	startLine, err := strconv.ParseUint(lines[0], 10, 64)
128	if err != nil {
129		return nil, err
130	}
131	endLine := startLine
132	if len(lines) > 1 {
133		endLine, err = strconv.ParseUint(lines[1], 10, 64)
134		if err != nil {
135			return nil, err
136		}
137	}
138
139	col, err := strconv.ParseUint(issue.Col, 10, 64)
140	if err != nil {
141		return nil, err
142	}
143
144	for _, rootPath := range rootPaths {
145		if strings.HasPrefix(issue.File, rootPath) {
146			filePath = strings.Replace(issue.File, rootPath+"/", "", 1)
147		}
148	}
149
150	location := &sarifLocation{
151		PhysicalLocation: &sarifPhysicalLocation{
152			ArtifactLocation: &sarifArtifactLocation{
153				URI: filePath,
154			},
155			Region: &sarifRegion{
156				StartLine:   startLine,
157				EndLine:     endLine,
158				StartColumn: col,
159				EndColumn:   col,
160			},
161		},
162	}
163
164	return location, nil
165}
166
167// From https://docs.oasis-open.org/sarif/sarif/v2.0/csprd02/sarif-v2.0-csprd02.html#_Toc10127839
168// * "warning": The rule specified by ruleId was evaluated and a problem was found.
169// * "error": The rule specified by ruleId was evaluated and a serious problem was found.
170// * "note": The rule specified by ruleId was evaluated and a minor problem or an opportunity to improve the code was found.
171func getSarifLevel(s string) sarifLevel {
172	switch s {
173	case "LOW":
174		return sarifWarning
175	case "MEDIUM":
176		return sarifError
177	case "HIGH":
178		return sarifError
179	default:
180		return sarifNote
181	}
182}
183