From a55313a1639958e8912cf3bced772783a69e5827 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha Date: Fri, 18 Oct 2024 16:32:04 +0100 Subject: [PATCH 1/5] Added rules to sarif. Added ruleDescription to other reports --- engine/engine.go | 19 ++++++++++--------- lib/reporting/sarif.go | 41 ++++++++++++++++++++++++++++++++++++----- lib/secrets/secret.go | 1 + 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index 322014b..fe1a4c6 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -90,15 +90,16 @@ func (e *Engine) Detect(item plugins.ISourceItem, secretsChannel chan *secrets.S endLine = value.EndLine } secret := &secrets.Secret{ - ID: itemId, - Source: item.GetSource(), - RuleID: value.RuleID, - StartLine: startLine, - StartColumn: value.StartColumn, - EndLine: endLine, - EndColumn: value.EndColumn, - Value: value.Secret, - LineContent: value.Line, + ID: itemId, + Source: item.GetSource(), + RuleID: value.RuleID, + StartLine: startLine, + StartColumn: value.StartColumn, + EndLine: endLine, + EndColumn: value.EndColumn, + Value: value.Secret, + LineContent: value.Line, + RuleDescription: value.Description, } if !isSecretIgnored(secret, &e.ignoredIds, &e.allowedValues) { secretsChannel <- secret diff --git a/lib/reporting/sarif.go b/lib/reporting/sarif.go index 67793bf..be694f6 100644 --- a/lib/reporting/sarif.go +++ b/lib/reporting/sarif.go @@ -3,9 +3,10 @@ package reporting import ( "encoding/json" "fmt" + "strings" + "github.com/checkmarx/2ms/lib/config" "github.com/checkmarx/2ms/lib/secrets" - "strings" ) func writeSarif(report Report, cfg *config.Config) (string, error) { @@ -26,23 +27,43 @@ func writeSarif(report Report, cfg *config.Config) (string, error) { func getRuns(report Report, cfg *config.Config) []Runs { return []Runs{ { - Tool: getTool(cfg), + Tool: getTool(report, cfg), Results: getResults(report), }, } } -func getTool(cfg *config.Config) Tool { +func getTool(report Report, cfg *config.Config) Tool { tool := Tool{ Driver: Driver{ Name: cfg.Name, SemanticVersion: cfg.Version, + Rules: getRules(report), }, } return tool } +func getRules(report Report) []*SarifRule { + uniqueRulesMap := make(map[string]*SarifRule) + var reportRules []*SarifRule + for _, reportSecrets := range report.Results { + for _, secret := range reportSecrets { + if _, exists := uniqueRulesMap[secret.RuleID]; !exists { + uniqueRulesMap[secret.RuleID] = &SarifRule{ + ID: secret.RuleID, + FullDescription: &MultiformatMessageString{ + Text: &secret.RuleDescription, + }, + } + reportRules = append(reportRules, uniqueRulesMap[secret.RuleID]) + } + } + } + return reportRules +} + func hasNoResults(report Report) bool { return len(report.Results) == 0 } @@ -112,14 +133,24 @@ type ShortDescription struct { } type Driver struct { - Name string `json:"name"` - SemanticVersion string `json:"semanticVersion"` + Name string `json:"name"` + SemanticVersion string `json:"semanticVersion"` + Rules []*SarifRule `json:"rules,omitempty"` } type Tool struct { Driver Driver `json:"driver"` } +type SarifRule struct { + ID string `json:"id"` + FullDescription *MultiformatMessageString `json:"fullDescription,omitempty"` +} + +type MultiformatMessageString struct { + Text *string `json:"text,omitempty"` +} + type Message struct { Text string `json:"text"` } diff --git a/lib/secrets/secret.go b/lib/secrets/secret.go index 0ca0996..2485d12 100644 --- a/lib/secrets/secret.go +++ b/lib/secrets/secret.go @@ -43,5 +43,6 @@ type Secret struct { EndColumn int `json:"endColumn"` Value string `json:"value"` ValidationStatus ValidationResult `json:"validationStatus,omitempty"` + RuleDescription string `json:"ruleDescription,omitempty"` ExtraDetails map[string]interface{} `json:"extraDetails,omitempty"` } From 9bec5e50348dbab618802adb87009ab27df5c481 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha Date: Tue, 22 Oct 2024 17:35:24 +0100 Subject: [PATCH 2/5] Added tests. Removed redundant message struct --- lib/reporting/report_test.go | 258 +++++++++++++++++++++++++++++++++++ lib/reporting/sarif.go | 12 +- 2 files changed, 262 insertions(+), 8 deletions(-) diff --git a/lib/reporting/report_test.go b/lib/reporting/report_test.go index d282c92..973d2ce 100644 --- a/lib/reporting/report_test.go +++ b/lib/reporting/report_test.go @@ -1,13 +1,61 @@ package reporting import ( + "encoding/json" "os" "path/filepath" "reflect" + "strings" "testing" "github.com/checkmarx/2ms/lib/config" "github.com/checkmarx/2ms/lib/secrets" + "github.com/stretchr/testify/assert" +) + +var ( + ruleID1 = "ruleID1" + ruleID2 = "ruleID2" + result1 = &secrets.Secret{ + ID: "ID1", + Source: "file1", + RuleID: ruleID1, + StartLine: 150, + EndLine: 150, + LineContent: "line content", + StartColumn: 31, + EndColumn: 150, + Value: "value", + ValidationStatus: secrets.ValidResult, + RuleDescription: "Rule Description", + } + result2 = &secrets.Secret{ + ID: "ID2", + Source: "file2", + RuleID: "ruleID1", + StartLine: 10, + EndLine: 10, + LineContent: "line content2", + StartColumn: 41, + EndColumn: 160, + Value: "value 2", + ValidationStatus: secrets.InvalidResult, + RuleDescription: "Rule Description", + } + // this result has a different rule than 1 and 2 + result3 = &secrets.Secret{ + ID: "ID3", + Source: "file3", + RuleID: ruleID2, + StartLine: 16, + EndLine: 16, + LineContent: "line content3", + StartColumn: 11, + EndColumn: 130, + Value: "value 3", + ValidationStatus: secrets.UnknownResult, + RuleDescription: "Rule Description2", + } ) func TestAddSecretToFile(t *testing.T) { @@ -57,3 +105,213 @@ func TestWriteReportInNonExistingDir(t *testing.T) { os.RemoveAll(filepath.Join(tempDir, "test_temp_dir")) } + +func TestGetOutputSarif(t *testing.T) { + tests := []struct { + name string + arg Report + want []Runs + wantErr bool + }{ + { + name: "two_results_same_rule_want_one_rule_in_report", + arg: Report{ + TotalItemsScanned: 2, + TotalSecretsFound: 2, + Results: map[string][]*secrets.Secret{ + "secret1": {result1}, + "secret2": {result2}, + }, + }, + wantErr: false, + want: []Runs{ + { + Tool: Tool{ + Driver: Driver{ + Name: "report", + SemanticVersion: "1", + Rules: []*SarifRule{ + {ID: "ruleID1", + FullDescription: &Message{ + Text: result1.RuleDescription, + }, + }, + }, + }, + }, + Results: []Results{ + { + Message: Message{ + Text: messageText(result1.RuleID, result1.Source), + }, + RuleId: ruleID1, + Locations: []Locations{ + { + PhysicalLocation: PhysicalLocation{ + ArtifactLocation: ArtifactLocation{ + URI: result1.Source, + }, + Region: Region{ + StartLine: result1.StartLine, + StartColumn: result1.StartColumn, + EndLine: result1.EndLine, + EndColumn: result1.EndColumn, + Snippet: Snippet{ + Text: result1.Value, + Properties: Properties{ + "lineContent": strings.TrimSpace(result1.LineContent), + }, + }, + }, + }, + }, + }, + Properties: Properties{ + "validationStatus": string(result1.ValidationStatus), + }, + }, + { + Message: Message{ + Text: messageText(result2.RuleID, result2.Source), + }, + RuleId: ruleID1, + Locations: []Locations{ + { + PhysicalLocation: PhysicalLocation{ + ArtifactLocation: ArtifactLocation{ + URI: result2.Source, + }, + Region: Region{ + StartLine: result2.StartLine, + StartColumn: result2.StartColumn, + EndLine: result2.EndLine, + EndColumn: result2.EndColumn, + Snippet: Snippet{ + Text: result2.Value, + Properties: Properties{ + "lineContent": strings.TrimSpace(result2.LineContent), + }, + }, + }, + }, + }, + }, + Properties: Properties{ + "validationStatus": string(result2.ValidationStatus), + }, + }, + }, + }, + }, + }, + { + name: "two_results_same_rule_want_two_rules_in_report", + arg: Report{ + TotalItemsScanned: 2, + TotalSecretsFound: 2, + Results: map[string][]*secrets.Secret{ + "secret1": {result1}, + "secret2": {result3}, + }, + }, + wantErr: false, + want: []Runs{ + { + Tool: Tool{ + Driver: Driver{ + Name: "report", + SemanticVersion: "1", + Rules: []*SarifRule{ + {ID: ruleID1, + FullDescription: &Message{ + Text: result1.RuleDescription, + }, + }, + {ID: ruleID2, + FullDescription: &Message{ + Text: result3.RuleDescription, + }, + }, + }, + }, + }, + Results: []Results{ + { + Message: Message{ + Text: messageText(result1.RuleID, result1.Source), + }, + RuleId: ruleID1, + Locations: []Locations{ + { + PhysicalLocation: PhysicalLocation{ + ArtifactLocation: ArtifactLocation{ + URI: result1.Source, + }, + Region: Region{ + StartLine: result1.StartLine, + StartColumn: result1.StartColumn, + EndLine: result1.EndLine, + EndColumn: result1.EndColumn, + Snippet: Snippet{ + Text: result1.Value, + Properties: Properties{ + "lineContent": strings.TrimSpace(result1.LineContent), + }, + }, + }, + }, + }, + }, + Properties: Properties{ + "validationStatus": string(result1.ValidationStatus), + }, + }, + { + Message: Message{ + Text: messageText(result3.RuleID, result3.Source), + }, + RuleId: ruleID2, + Locations: []Locations{ + { + PhysicalLocation: PhysicalLocation{ + ArtifactLocation: ArtifactLocation{ + URI: result3.Source, + }, + Region: Region{ + StartLine: result3.StartLine, + StartColumn: result3.StartColumn, + EndLine: result3.EndLine, + EndColumn: result3.EndColumn, + Snippet: Snippet{ + Text: result3.Value, + Properties: Properties{ + "lineContent": strings.TrimSpace(result3.LineContent), + }, + }, + }, + }, + }, + }, + Properties: Properties{ + "validationStatus": string(result3.ValidationStatus), + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.arg.getOutput(sarifFormat, &config.Config{Name: "report", Version: "1"}) + if tt.wantErr { + assert.NotNil(t, err) + return + } + var gotReport Sarif + err = json.Unmarshal([]byte(got), &gotReport) + assert.Equal(t, tt.want, gotReport.Runs) + }) + } +} diff --git a/lib/reporting/sarif.go b/lib/reporting/sarif.go index be694f6..090931a 100644 --- a/lib/reporting/sarif.go +++ b/lib/reporting/sarif.go @@ -53,8 +53,8 @@ func getRules(report Report) []*SarifRule { if _, exists := uniqueRulesMap[secret.RuleID]; !exists { uniqueRulesMap[secret.RuleID] = &SarifRule{ ID: secret.RuleID, - FullDescription: &MultiformatMessageString{ - Text: &secret.RuleDescription, + FullDescription: &Message{ + Text: secret.RuleDescription, }, } reportRules = append(reportRules, uniqueRulesMap[secret.RuleID]) @@ -143,12 +143,8 @@ type Tool struct { } type SarifRule struct { - ID string `json:"id"` - FullDescription *MultiformatMessageString `json:"fullDescription,omitempty"` -} - -type MultiformatMessageString struct { - Text *string `json:"text,omitempty"` + ID string `json:"id"` + FullDescription *Message `json:"fullDescription,omitempty"` } type Message struct { From a0864908bd4f7b63727bad8a91c04031cb3f96f6 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha Date: Tue, 22 Oct 2024 18:15:32 +0100 Subject: [PATCH 3/5] Added methods to sort rules and results in tests --- lib/reporting/report_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lib/reporting/report_test.go b/lib/reporting/report_test.go index 973d2ce..408de75 100644 --- a/lib/reporting/report_test.go +++ b/lib/reporting/report_test.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "reflect" + "sort" "strings" "testing" @@ -311,7 +312,36 @@ func TestGetOutputSarif(t *testing.T) { } var gotReport Sarif err = json.Unmarshal([]byte(got), &gotReport) + SortSarifReports(&gotReport, &Sarif{Runs: tt.want}) assert.Equal(t, tt.want, gotReport.Runs) }) } } + +// SortProject Sorts two sarif reports +func SortSarifReports(run1, run2 *Sarif) { + // Sort Rules + SortRules(run1.Runs[0].Tool.Driver.Rules, run2.Runs[0].Tool.Driver.Rules) + SortResults(run1.Runs[0].Results, run2.Runs[0].Results) + +} + +func SortRules(rules1, rules2 []*SarifRule) { + // Sort both slices + sort.Slice(rules1, func(i, j int) bool { + return rules1[i].ID < rules1[j].ID + }) + sort.Slice(rules2, func(i, j int) bool { + return rules2[i].ID < rules2[j].ID + }) +} + +func SortResults(results1, results2 []Results) { + // Sort both slices + sort.Slice(results1, func(i, j int) bool { + return results1[i].Message.Text < results1[j].Message.Text + }) + sort.Slice(results2, func(i, j int) bool { + return results2[i].Message.Text < results2[j].Message.Text + }) +} From fffb640f12416b9479375133b06127907baae4af Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha Date: Wed, 23 Oct 2024 14:25:18 +0100 Subject: [PATCH 4/5] Fixing linter and trivy issues --- Dockerfile | 2 +- lib/reporting/report_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6e64b7d..4654d78 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ RUN go build -o /app/2ms . # Runtime image FROM cgr.dev/chainguard/git@sha256:02660563e96b553d6aeb4093e3fcc3e91b2ad3a86e05c65b233f37f035e5044e -RUN apk add --no-cache bash=5.2.21-r1 git=2.45.1-r0 git-lfs=3.5.1-r8 libcurl-openssl4=8.10.0-r0 glibc=2.39-r5 glibc-locale-posix=2.39-r5 ld-linux==2.39-r5 libcrypt1=2.39-r5 && git config --global --add safe.directory /repo +RUN apk add --no-cache bash=5.2.21-r1 git=2.45.1-r0 git-lfs=3.5.1-r8 libcurl-openssl4=8.10.0-r0 glibc=2.39-r5 glibc-locale-posix=2.39-r5 ld-linux==2.39-r5 libcrypt1=2.39-r5 libcrypto3=3.3.2-r2 libssl3=3.3.2-r2 && git config --global --add safe.directory /repo COPY --from=builder /app/2ms . diff --git a/lib/reporting/report_test.go b/lib/reporting/report_test.go index 408de75..d6bc02b 100644 --- a/lib/reporting/report_test.go +++ b/lib/reporting/report_test.go @@ -312,6 +312,7 @@ func TestGetOutputSarif(t *testing.T) { } var gotReport Sarif err = json.Unmarshal([]byte(got), &gotReport) + assert.Nil(t, err) SortSarifReports(&gotReport, &Sarif{Runs: tt.want}) assert.Equal(t, tt.want, gotReport.Runs) }) From e4af4e5b920b2038eabdd41e39e7941fb138e6cb Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha Date: Wed, 23 Oct 2024 15:40:46 +0100 Subject: [PATCH 5/5] Put sarif rule and results at begin of tests in order to reduce repetition in want of unit tests --- lib/reporting/report_test.go | 268 ++++++++++++++++------------------- 1 file changed, 125 insertions(+), 143 deletions(-) diff --git a/lib/reporting/report_test.go b/lib/reporting/report_test.go index d6bc02b..946059c 100644 --- a/lib/reporting/report_test.go +++ b/lib/reporting/report_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" ) +// test input results var ( ruleID1 = "ruleID1" ruleID2 = "ruleID2" @@ -30,10 +31,11 @@ var ( ValidationStatus: secrets.ValidResult, RuleDescription: "Rule Description", } + // this result has a different rule than result1 result2 = &secrets.Secret{ ID: "ID2", Source: "file2", - RuleID: "ruleID1", + RuleID: ruleID2, StartLine: 10, EndLine: 10, LineContent: "line content2", @@ -41,13 +43,13 @@ var ( EndColumn: 160, Value: "value 2", ValidationStatus: secrets.InvalidResult, - RuleDescription: "Rule Description", + RuleDescription: "Rule Description2", } - // this result has a different rule than 1 and 2 + // this result has the same rule as result1 result3 = &secrets.Secret{ ID: "ID3", Source: "file3", - RuleID: ruleID2, + RuleID: ruleID1, StartLine: 16, EndLine: 16, LineContent: "line content3", @@ -55,7 +57,115 @@ var ( EndColumn: 130, Value: "value 3", ValidationStatus: secrets.UnknownResult, - RuleDescription: "Rule Description2", + RuleDescription: "Rule Description", + } +) + +// test expected outputs +var ( + // sarif rules + rule1Sarif = &SarifRule{ + ID: ruleID1, + FullDescription: &Message{ + Text: result1.RuleDescription, + }, + } + rule2Sarif = &SarifRule{ + ID: ruleID2, + FullDescription: &Message{ + Text: result2.RuleDescription, + }, + } + // sarif results + result1Sarif = Results{ + Message: Message{ + Text: messageText(result1.RuleID, result1.Source), + }, + RuleId: ruleID1, + Locations: []Locations{ + { + PhysicalLocation: PhysicalLocation{ + ArtifactLocation: ArtifactLocation{ + URI: result1.Source, + }, + Region: Region{ + StartLine: result1.StartLine, + StartColumn: result1.StartColumn, + EndLine: result1.EndLine, + EndColumn: result1.EndColumn, + Snippet: Snippet{ + Text: result1.Value, + Properties: Properties{ + "lineContent": strings.TrimSpace(result1.LineContent), + }, + }, + }, + }, + }, + }, + Properties: Properties{ + "validationStatus": string(result1.ValidationStatus), + }, + } + result2Sarif = Results{ + Message: Message{ + Text: messageText(result2.RuleID, result2.Source), + }, + RuleId: ruleID2, + Locations: []Locations{ + { + PhysicalLocation: PhysicalLocation{ + ArtifactLocation: ArtifactLocation{ + URI: result2.Source, + }, + Region: Region{ + StartLine: result2.StartLine, + StartColumn: result2.StartColumn, + EndLine: result2.EndLine, + EndColumn: result2.EndColumn, + Snippet: Snippet{ + Text: result2.Value, + Properties: Properties{ + "lineContent": strings.TrimSpace(result2.LineContent), + }, + }, + }, + }, + }, + }, + Properties: Properties{ + "validationStatus": string(result2.ValidationStatus), + }, + } + result3Sarif = Results{ + Message: Message{ + Text: messageText(result3.RuleID, result3.Source), + }, + RuleId: ruleID1, + Locations: []Locations{ + { + PhysicalLocation: PhysicalLocation{ + ArtifactLocation: ArtifactLocation{ + URI: result3.Source, + }, + Region: Region{ + StartLine: result3.StartLine, + StartColumn: result3.StartColumn, + EndLine: result3.EndLine, + EndColumn: result3.EndColumn, + Snippet: Snippet{ + Text: result3.Value, + Properties: Properties{ + "lineContent": strings.TrimSpace(result3.LineContent), + }, + }, + }, + }, + }, + }, + Properties: Properties{ + "validationStatus": string(result3.ValidationStatus), + }, } ) @@ -121,7 +231,7 @@ func TestGetOutputSarif(t *testing.T) { TotalSecretsFound: 2, Results: map[string][]*secrets.Secret{ "secret1": {result1}, - "secret2": {result2}, + "secret3": {result3}, }, }, wantErr: false, @@ -132,87 +242,25 @@ func TestGetOutputSarif(t *testing.T) { Name: "report", SemanticVersion: "1", Rules: []*SarifRule{ - {ID: "ruleID1", - FullDescription: &Message{ - Text: result1.RuleDescription, - }, - }, + rule1Sarif, }, }, }, Results: []Results{ - { - Message: Message{ - Text: messageText(result1.RuleID, result1.Source), - }, - RuleId: ruleID1, - Locations: []Locations{ - { - PhysicalLocation: PhysicalLocation{ - ArtifactLocation: ArtifactLocation{ - URI: result1.Source, - }, - Region: Region{ - StartLine: result1.StartLine, - StartColumn: result1.StartColumn, - EndLine: result1.EndLine, - EndColumn: result1.EndColumn, - Snippet: Snippet{ - Text: result1.Value, - Properties: Properties{ - "lineContent": strings.TrimSpace(result1.LineContent), - }, - }, - }, - }, - }, - }, - Properties: Properties{ - "validationStatus": string(result1.ValidationStatus), - }, - }, - { - Message: Message{ - Text: messageText(result2.RuleID, result2.Source), - }, - RuleId: ruleID1, - Locations: []Locations{ - { - PhysicalLocation: PhysicalLocation{ - ArtifactLocation: ArtifactLocation{ - URI: result2.Source, - }, - Region: Region{ - StartLine: result2.StartLine, - StartColumn: result2.StartColumn, - EndLine: result2.EndLine, - EndColumn: result2.EndColumn, - Snippet: Snippet{ - Text: result2.Value, - Properties: Properties{ - "lineContent": strings.TrimSpace(result2.LineContent), - }, - }, - }, - }, - }, - }, - Properties: Properties{ - "validationStatus": string(result2.ValidationStatus), - }, - }, + result1Sarif, + result3Sarif, }, }, }, }, { - name: "two_results_same_rule_want_two_rules_in_report", + name: "two_results_two_rules_want_two_rules_in_report", arg: Report{ TotalItemsScanned: 2, TotalSecretsFound: 2, Results: map[string][]*secrets.Secret{ "secret1": {result1}, - "secret2": {result3}, + "secret2": {result2}, }, }, wantErr: false, @@ -223,80 +271,14 @@ func TestGetOutputSarif(t *testing.T) { Name: "report", SemanticVersion: "1", Rules: []*SarifRule{ - {ID: ruleID1, - FullDescription: &Message{ - Text: result1.RuleDescription, - }, - }, - {ID: ruleID2, - FullDescription: &Message{ - Text: result3.RuleDescription, - }, - }, + rule1Sarif, + rule2Sarif, }, }, }, Results: []Results{ - { - Message: Message{ - Text: messageText(result1.RuleID, result1.Source), - }, - RuleId: ruleID1, - Locations: []Locations{ - { - PhysicalLocation: PhysicalLocation{ - ArtifactLocation: ArtifactLocation{ - URI: result1.Source, - }, - Region: Region{ - StartLine: result1.StartLine, - StartColumn: result1.StartColumn, - EndLine: result1.EndLine, - EndColumn: result1.EndColumn, - Snippet: Snippet{ - Text: result1.Value, - Properties: Properties{ - "lineContent": strings.TrimSpace(result1.LineContent), - }, - }, - }, - }, - }, - }, - Properties: Properties{ - "validationStatus": string(result1.ValidationStatus), - }, - }, - { - Message: Message{ - Text: messageText(result3.RuleID, result3.Source), - }, - RuleId: ruleID2, - Locations: []Locations{ - { - PhysicalLocation: PhysicalLocation{ - ArtifactLocation: ArtifactLocation{ - URI: result3.Source, - }, - Region: Region{ - StartLine: result3.StartLine, - StartColumn: result3.StartColumn, - EndLine: result3.EndLine, - EndColumn: result3.EndColumn, - Snippet: Snippet{ - Text: result3.Value, - Properties: Properties{ - "lineContent": strings.TrimSpace(result3.LineContent), - }, - }, - }, - }, - }, - }, - Properties: Properties{ - "validationStatus": string(result3.ValidationStatus), - }, - }, + result1Sarif, + result2Sarif, }, }, },