-
Notifications
You must be signed in to change notification settings - Fork 0
/
reportPatch.go
271 lines (236 loc) · 8.4 KB
/
reportPatch.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
package cmd
import (
"encoding/json"
"fmt"
"io"
"os"
"strings"
"github.com/owenrumney/go-sarif/v2/sarif"
"github.com/spf13/cobra"
)
type RunOptionsReportPatch struct {
Input string
Output string
WhenRule string
WhenTextContains []string
WhenTextNotContains []string
WhenLocationContains []string
SetSeverity string
Delete bool
}
var allArgumentsReportPatch RunOptionsReportPatch
const (
OperationWhenRule = "OperationWhenRule"
OperationWhenTextContains = "OperationWhenTextContains"
OperationWhenTextNotContains = "OperationWhenTextNotContains"
OperationWhenLocationContains = "OperationWhenLocationContains"
)
type Condition struct {
Operation string
Value string
}
func isResultSpecific(conditions []Condition) bool {
resultSpecificConditions := []string{OperationWhenTextContains, OperationWhenTextNotContains, OperationWhenLocationContains}
for _, condition := range conditions {
for _, op := range resultSpecificConditions {
if condition.Operation == op {
return true
}
}
}
return false
}
func buildConditions() []Condition {
conditions := []Condition{}
if len(allArgumentsReportPatch.WhenRule) != 0 {
conditions = append(conditions, Condition{
Operation: OperationWhenRule,
Value: allArgumentsReportPatch.WhenRule,
})
}
if len(allArgumentsReportPatch.WhenTextContains) != 0 {
for _, text := range allArgumentsReportPatch.WhenTextContains {
conditions = append(conditions, Condition{
Operation: OperationWhenTextContains,
Value: text,
})
}
}
if len(allArgumentsReportPatch.WhenTextNotContains) != 0 {
for _, text := range allArgumentsReportPatch.WhenTextNotContains {
conditions = append(conditions, Condition{
Operation: OperationWhenTextNotContains,
Value: text,
})
}
}
if len(allArgumentsReportPatch.WhenLocationContains) != 0 {
for _, loc := range allArgumentsReportPatch.WhenLocationContains {
conditions = append(conditions, Condition{
Operation: OperationWhenLocationContains,
Value: loc,
})
}
}
return conditions
}
func copyReport(orig *sarif.Report) (*sarif.Report, error) {
origJSON, err := json.Marshal(orig)
if err != nil {
return nil, err
}
clone := sarif.Report{}
if err = json.Unmarshal(origJSON, &clone); err != nil {
return nil, err
}
return &clone, nil
}
func toSarifLevel(severity string) string {
switch severity {
case "high":
return "error"
case "medium":
return "warning"
case "low":
return "note"
default:
return "none"
}
}
// If any of conditions don't match - returns false
// otherwise returns true
func evalConditionsForRule(rule *sarif.ReportingDescriptor, conditions []Condition) bool {
for _, condition := range conditions {
switch condition.Operation {
case OperationWhenRule:
if !strings.Contains(rule.ID, condition.Value) {
return false
}
}
}
return true
}
func applyRulePatch(rule *sarif.ReportingDescriptor) {
if len(allArgumentsReportPatch.SetSeverity) > 0 {
rule.DefaultConfiguration.Level = toSarifLevel(allArgumentsReportPatch.SetSeverity)
}
}
func patchRules(report *sarif.Report, conditions []Condition) {
for _, run := range report.Runs {
for _, rule := range run.Tool.Driver.Rules {
if !evalConditionsForRule(rule, conditions) {
continue
}
applyRulePatch(rule)
}
}
}
// If any of conditions don't match - returns false
// otherwise returns true
func evalConditionsForResult(result *sarif.Result, conditions []Condition) bool {
for _, condition := range conditions {
switch condition.Operation {
case OperationWhenRule:
if result.RuleID == nil || !strings.Contains(*result.RuleID, condition.Value) {
return false
}
case OperationWhenTextContains:
if result.Message.Text == nil || !strings.Contains(*result.Message.Text, condition.Value) {
return false
}
case OperationWhenTextNotContains:
if result.Message.Text != nil && strings.Contains(*result.Message.Text, condition.Value) {
return false
}
case OperationWhenLocationContains:
for _, location := range result.Locations {
if location.PhysicalLocation.ArtifactLocation.URI == nil || !strings.Contains(*location.PhysicalLocation.ArtifactLocation.URI, condition.Value) {
return false
}
}
}
}
return true
}
func applyResultPatch(result *sarif.Result) {
if len(allArgumentsReportPatch.SetSeverity) > 0 {
*result.Level = toSarifLevel(allArgumentsReportPatch.SetSeverity)
}
}
func patchResultsForDelete(report *sarif.Report, conditions []Condition) {
for _, run := range report.Runs {
results := []*sarif.Result{}
for _, result := range run.Results {
if evalConditionsForResult(result, conditions) {
continue
}
results = append(results, result)
}
run.Results = results
}
}
func patchResultsForSet(report *sarif.Report, conditions []Condition) {
for _, run := range report.Runs {
for _, result := range run.Results {
if !evalConditionsForResult(result, conditions) {
continue
}
applyResultPatch(result)
}
}
}
func patchResults(report *sarif.Report, conditions []Condition) {
if allArgumentsReportPatch.Delete {
patchResultsForDelete(report, conditions)
} else {
patchResultsForSet(report, conditions)
}
}
var execExampleReportPatch = ` # Overwrite severity for specific rule by rule id and results for this rule
scanio report-patch -i original-report.sarif -o patched-report.sarif --when-rule java/CSRFDisabled --set-severity low
# Set lower criticality for results of specific rule, when description says that source of malicious data is coming from command line arguments.
scanio report-patch -i original-report.sarif -o patched-report.sarif --when-rule javascript/SQLInjection --when-text-contains "input from a command line argument" --set-severity low
# Delete results found in node_modules subfolder
scanio report-patch -i original-report.sarif -o patched-report.sarif --when-location-contains node_modules/ --delete`
// reportPatchCmd represents the reportPatch command
var reportPatchCmd = &cobra.Command{
Use: "report-patch",
Short: "This command allows to overwrite some data in sarif report. For example you can decrease severity for some falsy rules, or make some severity adjustments depending on fionding description, finding location and so on.",
Example: execExampleReportPatch,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("reportPatch called")
jsonFile, err := os.Open(allArgumentsReportPatch.Input)
if err != nil {
return err
}
defer jsonFile.Close()
var originalSarifReport sarif.Report
byteValue, _ := io.ReadAll(jsonFile)
json.Unmarshal([]byte(byteValue), &originalSarifReport)
report, err := copyReport(&originalSarifReport)
if err != nil {
return err
}
conditions := buildConditions()
if !isResultSpecific(conditions) {
patchRules(report, conditions)
}
patchResults(report, conditions)
if len(allArgumentsReportPatch.Output) > 0 {
data, _ := json.MarshalIndent(report, "", " ")
_ = os.WriteFile(allArgumentsReportPatch.Output, data, 0644)
}
return nil
},
}
func init() {
rootCmd.AddCommand(reportPatchCmd)
reportPatchCmd.Flags().StringVarP(&allArgumentsReportPatch.Input, "input", "i", "", "input file with SAST report ion SARIF format")
reportPatchCmd.Flags().StringVarP(&allArgumentsReportPatch.Output, "output", "o", "", "output file where patched SARIF report will be saved to")
reportPatchCmd.Flags().StringVar(&allArgumentsReportPatch.WhenRule, "when-rule", "", "condition to apply patch only to rules like this (check substring, because for semgrep when use local rules rule name contains full path)")
reportPatchCmd.Flags().StringArrayVar(&allArgumentsReportPatch.WhenTextContains, "when-text-contains", []string{}, "condition to apply patch only to results with description like this")
reportPatchCmd.Flags().StringArrayVar(&allArgumentsReportPatch.WhenTextNotContains, "when-text-not-contains", []string{}, "condition to apply patch only to results with description not like this")
reportPatchCmd.Flags().StringArrayVar(&allArgumentsReportPatch.WhenLocationContains, "when-location-contains", []string{}, "condition to apply patch only to results with specific location")
reportPatchCmd.Flags().StringVar(&allArgumentsReportPatch.SetSeverity, "set-severity", "", "patch severity level (high, medum, low, none)")
reportPatchCmd.Flags().BoolVar(&allArgumentsReportPatch.Delete, "delete", false, "delete finding that matches conditions")
}