From 45b2f8f5990a755a04de19e37541e04ce120addb Mon Sep 17 00:00:00 2001 From: Or Toren Date: Wed, 3 Jun 2026 13:37:21 +0300 Subject: [PATCH 1/4] services scan support in pull request --- go.mod | 4 +- go.sum | 4 - scanpullrequest/scanpullrequest.go | 20 ++- scanpullrequest/scanpullrequest_test.go | 40 ++++++ .../services_review_content_simplified.md | 30 +++++ .../services_review_content_standard.md | 23 ++++ ...ces_violation_review_content_simplified.md | 30 +++++ ...vices_violation_review_content_standard.md | 23 ++++ .../summary/summary_both_simplified.md | 1 + .../summary/summary_both_standard.md | 1 + .../summary/summary_simplified.md | 1 + .../summary/summary_standard.md | 1 + .../summary/summary_violation_simplified.md | 1 + .../summary/summary_violation_standard.md | 1 + utils/comment.go | 13 ++ utils/comment_test.go | 68 +++++++++++ utils/issues/issuescollection.go | 42 ++++++- utils/issues/issuescollection_test.go | 85 +++++++++++-- utils/outputwriter/outputcontent.go | 46 ++++++- utils/outputwriter/outputcontent_test.go | 114 ++++++++++++++++++ utils/scandetails.go | 2 +- utils/utils.go | 16 ++- 22 files changed, 542 insertions(+), 24 deletions(-) create mode 100644 testdata/messages/reviewcomment/services/services_review_content_simplified.md create mode 100644 testdata/messages/reviewcomment/services/services_review_content_standard.md create mode 100644 testdata/messages/reviewcomment/services/services_violation_review_content_simplified.md create mode 100644 testdata/messages/reviewcomment/services/services_violation_review_content_standard.md diff --git a/go.mod b/go.mod index f364f4b23..a24337f64 100644 --- a/go.mod +++ b/go.mod @@ -129,7 +129,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -// replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security dev +replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev @@ -137,6 +137,6 @@ require ( // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go master +replace github.com/jfrog/jfrog-client-go => ../jfrog-client-go // replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go master diff --git a/go.sum b/go.sum index fbf77ca41..0b01923ac 100644 --- a/go.sum +++ b/go.sum @@ -150,10 +150,6 @@ github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260528123948-61478692b94e h1:q github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260528123948-61478692b94e/go.mod h1:GQEGVW3wT1XPykXNsEiPQrF8/+01JvDVcGGYb5vqJuE= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260528061115-b41c87af0194 h1:cwppCKLitT0XBqYGQimW00qyx1ej88sY+rIjXAWNvAU= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260528061115-b41c87af0194/go.mod h1:9R90mhbczGXwW5EGlDs7F08ejQU/xdoDhYHMvzBiqgE= -github.com/jfrog/jfrog-cli-security v1.29.3 h1:cIoDn5NkhmrVANUr22H2IVwYjqeFTA+e61lb4qE+8X8= -github.com/jfrog/jfrog-cli-security v1.29.3/go.mod h1:wTdl1sSLyq+TzOPnncxBBhqCKEqF2kp9l86k+Y5E3mM= -github.com/jfrog/jfrog-client-go v1.55.1-0.20260528115006-6ca9682a3255 h1:CIOMO1Hj5N6PaIu7sJZ9bPowcibkcaWDulM2R6LHO9o= -github.com/jfrog/jfrog-client-go v1.55.1-0.20260528115006-6ca9682a3255/go.mod h1:FHpjN1nTDoj96xd6obe27EOgGErqzU0rQgC96L3Ch9E= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index 286acb511..125b78040 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -4,11 +4,12 @@ import ( "context" "errors" "fmt" - "github.com/jfrog/gofrog/datastructures" "os" "path/filepath" "strings" + "github.com/jfrog/gofrog/datastructures" + "github.com/jfrog/froggit-go/vcsclient" "github.com/jfrog/jfrog-cli-security/utils/formats" "github.com/jfrog/jfrog-cli-security/utils/jasutils" @@ -193,6 +194,7 @@ func filterFailedResultsIfScannersFailuresAreAllowed(targetResults, sourceResult filterScaResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepContextualAnalysis) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepSecrets) + filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepServices) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepIaC) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepSast) } @@ -202,6 +204,7 @@ func filterFailedResultsIfScannersFailuresAreAllowed(targetResults, sourceResult filterScaResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepContextualAnalysis) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepSecrets) + filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepServices) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepIaC) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepSast) } @@ -211,6 +214,7 @@ func filterFailedResultsIfScannersFailuresAreAllowed(targetResults, sourceResult filterScaResultsIfScanFailed(nil, sourceResult) filterJasResultsIfScanFailed(nil, sourceResult, results.CmdStepContextualAnalysis) filterJasResultsIfScanFailed(nil, sourceResult, results.CmdStepSecrets) + filterJasResultsIfScanFailed(nil, sourceResult, results.CmdStepServices) filterJasResultsIfScanFailed(nil, sourceResult, results.CmdStepIaC) filterJasResultsIfScanFailed(nil, sourceResult, results.CmdStepSast) } @@ -254,6 +258,12 @@ func filterSpecificScannersViolationsIfScanFailed(sourceResults *results.Securit sourceResults.Violations.Secrets = nil } + if (sourceStatusCodes.ServicesScanStatusCode != nil && *sourceStatusCodes.ServicesScanStatusCode != 0) || + (targetStatusCodes.ServicesScanStatusCode != nil && *targetStatusCodes.ServicesScanStatusCode != 0) { + log.Debug(fmt.Sprintf(violationsFilteringErrorMessage, results.CmdStepServices)) + sourceResults.Violations.Services = nil + } + if (sourceStatusCodes.IacScanStatusCode != nil && *sourceStatusCodes.IacScanStatusCode != 0) || (targetStatusCodes.IacScanStatusCode != nil && *targetStatusCodes.IacScanStatusCode != 0) { log.Debug(fmt.Sprintf(violationsFilteringErrorMessage, results.CmdStepIaC)) @@ -352,6 +362,10 @@ func filterJasResultsIfScanFailed(targetResult, sourceResult *results.TargetResu if sourceResult.JasResults != nil { sourceResult.JasResults.JasVulnerabilities.SecretsScanResults = nil } + case results.CmdStepServices: + if sourceResult.JasResults != nil { + sourceResult.JasResults.JasVulnerabilities.ServicesScanResults = nil + } case results.CmdStepIaC: if sourceResult.JasResults != nil { sourceResult.JasResults.JasVulnerabilities.IacScanResults = nil @@ -443,6 +457,9 @@ func scanResultsToIssuesCollection(scanResults *results.SecurityCommandResults, SecretsVulnerabilities: simpleJsonResults.SecretsVulnerabilities, SecretsViolations: simpleJsonResults.SecretsViolations, + ServicesVulnerabilities: simpleJsonResults.ServicesVulnerabilities, + ServicesViolations: simpleJsonResults.ServicesViolations, + SastVulnerabilities: simpleJsonResults.SastVulnerabilities, SastViolations: simpleJsonResults.SastViolations, } @@ -478,6 +495,7 @@ func getScanStatus(cmdResults ...formats.SimpleJsonResults) formats.ScanStatus { statuses.ScaStatusCode = getWorstScanStatus(statuses.ScaStatusCode, sourceResults.Statuses.ScaStatusCode) statuses.IacStatusCode = getWorstScanStatus(statuses.IacStatusCode, sourceResults.Statuses.IacStatusCode) statuses.SecretsStatusCode = getWorstScanStatus(statuses.SecretsStatusCode, sourceResults.Statuses.SecretsStatusCode) + statuses.ServicesStatusCode = getWorstScanStatus(statuses.ServicesStatusCode, sourceResults.Statuses.ServicesStatusCode) statuses.SastStatusCode = getWorstScanStatus(statuses.SastStatusCode, sourceResults.Statuses.SastStatusCode) statuses.ApplicabilityStatusCode = getWorstScanStatus(statuses.ApplicabilityStatusCode, sourceResults.Statuses.ApplicabilityStatusCode) } diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index a8c8932b6..bbb31ce24 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -76,6 +76,7 @@ func TestScanResultsToIssuesCollection(t *testing.T) { ContextualAnalysisStatusCode: securityutils.NewIntPtr(0), IacScanStatusCode: securityutils.NewIntPtr(0), SecretsScanStatusCode: securityutils.NewIntPtr(0), + ServicesScanStatusCode: securityutils.NewIntPtr(0), SastScanStatusCode: securityutils.NewIntPtr(0), }, ScanTarget: results.ScanTarget{Target: "dummy"}, @@ -121,6 +122,21 @@ func TestScanResultsToIssuesCollection(t *testing.T) { ), ), }, + ServicesScanResults: func() []*sarif.Run { + serviceRule := sarif.NewRule("service-rule") + serviceResult := sarifutils.CreateResultWithProperties( + "Exposed service endpoint", "service-rule", + severityutils.SeverityToSarifSeverityLevel(severityutils.High).String(), + map[string]string{ + sarifutils.CWEPropertyKey: "CWE-918", + sarifutils.OutcomesPropertyKey: "publicly-exposed", + }, + sarifutils.CreateLocation("service.yaml", 3, 4, 5, 6, "port: 8080"), + ) + serviceRun := sarifutils.CreateRunWithDummyResults(serviceResult) + serviceRun.Tool.Driver.Rules = []*sarif.ReportingDescriptor{serviceRule} + return []*sarif.Run{serviceRun} + }(), }, }, }}} @@ -208,6 +224,28 @@ func TestScanResultsToIssuesCollection(t *testing.T) { }, }, }, + ServicesVulnerabilities: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + SeverityNumValue: 31, + }, + Finding: "Exposed service endpoint", + Outcomes: "publicly-exposed", + ScannerInfo: formats.ScannerInfo{ + RuleId: "service-rule", + Cwe: []string{"CWE-918"}, + }, + Location: formats.Location{ + File: "service.yaml", + StartLine: 3, + StartColumn: 4, + EndLine: 5, + EndColumn: 6, + Snippet: "port: 8080", + }, + }, + }, } issuesRows, err := scanResultsToIssuesCollection(auditResults) @@ -217,6 +255,7 @@ func TestScanResultsToIssuesCollection(t *testing.T) { assert.ElementsMatch(t, expectedOutput.IacVulnerabilities, issuesRows.IacVulnerabilities) assert.ElementsMatch(t, expectedOutput.SecretsVulnerabilities, issuesRows.SecretsVulnerabilities) assert.ElementsMatch(t, expectedOutput.SastVulnerabilities, issuesRows.SastVulnerabilities) + assert.ElementsMatch(t, expectedOutput.ServicesVulnerabilities, issuesRows.ServicesVulnerabilities) assert.ElementsMatch(t, expectedOutput.LicensesViolations, issuesRows.LicensesViolations) } } @@ -1333,6 +1372,7 @@ func TestIsScanFailedInSourceOrTarget(t *testing.T) { } func preparePullRequestTest(t *testing.T, projectName string) (utils.Repository, vcsclient.VcsClient, func()) { + utils.SkipIfNoJFrogEnv(t) params, restoreEnv := utils.VerifyEnv(t) // Set test-specific environment variables diff --git a/testdata/messages/reviewcomment/services/services_review_content_simplified.md b/testdata/messages/reviewcomment/services/services_review_content_simplified.md new file mode 100644 index 000000000..c820db1f0 --- /dev/null +++ b/testdata/messages/reviewcomment/services/services_review_content_simplified.md @@ -0,0 +1,30 @@ + + +--- +## 🔌 Services Vulnerability + +--- +| Severity | CWE | Outcomes | Finding | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| High | CWE-918 | publicly-exposed | Exposed service endpoint | + + +--- +### Full description + +--- + + + +--- +### Vulnerability Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-918 | +| **Rule ID:** | service-rule | +| **CWE:** | CWE-918 | + +Services scanner description + diff --git a/testdata/messages/reviewcomment/services/services_review_content_standard.md b/testdata/messages/reviewcomment/services/services_review_content_standard.md new file mode 100644 index 000000000..56d7ce159 --- /dev/null +++ b/testdata/messages/reviewcomment/services/services_review_content_standard.md @@ -0,0 +1,23 @@ + +## 🔌 Services Vulnerability +
+ +| Severity | CWE | Outcomes | Finding | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | CWE-918 | publicly-exposed | Exposed service endpoint | + +
+ + +
Full description + +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-918 | +| **Rule ID:** | service-rule | +| **CWE:** | CWE-918 | + +Services scanner description + +
\ No newline at end of file diff --git a/testdata/messages/reviewcomment/services/services_violation_review_content_simplified.md b/testdata/messages/reviewcomment/services/services_violation_review_content_simplified.md new file mode 100644 index 000000000..e06f88901 --- /dev/null +++ b/testdata/messages/reviewcomment/services/services_violation_review_content_simplified.md @@ -0,0 +1,30 @@ + + +--- +## 🔌 Services Violation + +--- +| Severity | ID | CWE | Outcomes | Finding | Watch Name | Policies | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| Critical | services-violation-id | CWE-200 | misconfigured-port | Misconfigured service port | services-watch | policy1 | + + +--- +### Full description + +--- + + + +--- +### Violation Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-200 | +| **Rule ID:** | service-rule | +| **CWE:** | CWE-200 | + +Services scanner description + diff --git a/testdata/messages/reviewcomment/services/services_violation_review_content_standard.md b/testdata/messages/reviewcomment/services/services_violation_review_content_standard.md new file mode 100644 index 000000000..9f6e1f1af --- /dev/null +++ b/testdata/messages/reviewcomment/services/services_violation_review_content_standard.md @@ -0,0 +1,23 @@ + +## 🔌 Services Violation +
+ +| Severity | ID | CWE | Outcomes | Finding | Watch Name | Policies | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| ![critical](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableCriticalSeverity.png)
Critical | services-violation-id | CWE-200 | misconfigured-port | Misconfigured service port | services-watch | policy1 | + +
+ + +
Full description + +### Violation Details +| | | +| --------------------- | :-----------------------------------: | +| **CWE:** | CWE-200 | +| **Rule ID:** | service-rule | +| **CWE:** | CWE-200 | + +Services scanner description + +
\ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_both_simplified.md b/testdata/messages/summarycomment/summary/summary_both_simplified.md index 9512be400..779a1bba0 100644 --- a/testdata/messages/summarycomment/summary/summary_both_simplified.md +++ b/testdata/messages/summarycomment/summary/summary_both_simplified.md @@ -12,4 +12,5 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | 4 Issues Found: 🔴 3 High, 🟡 1 Low | | **Secrets** | ✅ Done | - | +| **Services** | â„šī¸ Not Scanned | - | | **Infrastructure as Code (IaC)** | â„šī¸ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_both_standard.md b/testdata/messages/summarycomment/summary/summary_both_standard.md index 30f1b29a4..cd434cd8d 100644 --- a/testdata/messages/summarycomment/summary/summary_both_standard.md +++ b/testdata/messages/summarycomment/summary/summary_both_standard.md @@ -8,4 +8,5 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done |
4 Issues Found 3 High
1 Low
| | **Secrets** | ✅ Done | - | +| **Services** | â„šī¸ Not Scanned | - | | **Infrastructure as Code (IaC)** | â„šī¸ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_simplified.md b/testdata/messages/summarycomment/summary/summary_simplified.md index 9a073c047..a06fe8d34 100644 --- a/testdata/messages/summarycomment/summary/summary_simplified.md +++ b/testdata/messages/summarycomment/summary/summary_simplified.md @@ -12,4 +12,5 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | 3 Issues Found: 🔴 2 High, 🟡 1 Low | | **Secrets** | ✅ Done | - | +| **Services** | â„šī¸ Not Scanned | - | | **Infrastructure as Code (IaC)** | â„šī¸ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_standard.md b/testdata/messages/summarycomment/summary/summary_standard.md index 5244ba6bf..34be3271c 100644 --- a/testdata/messages/summarycomment/summary/summary_standard.md +++ b/testdata/messages/summarycomment/summary/summary_standard.md @@ -8,4 +8,5 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done |
3 Issues Found 2 High
1 Low
| | **Secrets** | ✅ Done | - | +| **Services** | â„šī¸ Not Scanned | - | | **Infrastructure as Code (IaC)** | â„šī¸ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_violation_simplified.md b/testdata/messages/summarycomment/summary/summary_violation_simplified.md index 58185dd84..738debc4d 100644 --- a/testdata/messages/summarycomment/summary/summary_violation_simplified.md +++ b/testdata/messages/summarycomment/summary/summary_violation_simplified.md @@ -12,4 +12,5 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | 1 Issues Found: 🔴 1 High | | **Secrets** | ✅ Done | - | +| **Services** | â„šī¸ Not Scanned | - | | **Infrastructure as Code (IaC)** | â„šī¸ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_violation_standard.md b/testdata/messages/summarycomment/summary/summary_violation_standard.md index 245da163e..47c9302ad 100644 --- a/testdata/messages/summarycomment/summary/summary_violation_standard.md +++ b/testdata/messages/summarycomment/summary/summary_violation_standard.md @@ -8,4 +8,5 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done |
1 Issues Found 1 High
| | **Secrets** | ✅ Done | - | +| **Services** | â„šī¸ Not Scanned | - | | **Infrastructure as Code (IaC)** | â„šī¸ Not Scanned | - | \ No newline at end of file diff --git a/utils/comment.go b/utils/comment.go index 9c9d0dd3d..093e225ec 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -33,6 +33,7 @@ const ( IacComment ReviewCommentType = "Iac" SastComment ReviewCommentType = "Sast" SecretComment ReviewCommentType = "Secrets" + ServicesComment ReviewCommentType = "Services" SnippetComment ReviewCommentType = "Snippet" snippetVersionMarker = "snippet" @@ -222,6 +223,16 @@ func getNewReviewComments(repo *Repository, issues *issues.ScansIssuesCollection } } + // Services review comments + for _, service := range issues.ServicesVulnerabilities { + commentsToAdd = append(commentsToAdd, generateReviewComment(ServicesComment, service.Location, generateSourceCodeReviewContent(ServicesComment, false, writer, service))) + } + if len(issues.ServicesViolations) > 0 { + for _, similarServicesIssues := range groupSimilarJasIssues(issues.ServicesViolations) { + commentsToAdd = append(commentsToAdd, generateReviewComment(ServicesComment, similarServicesIssues.Location, generateSourceCodeReviewContent(ServicesComment, true, writer, similarServicesIssues.issues...))) + } + } + // Secrets review comments if !repo.FrogbotConfig.ShowSecretsAsPrComment { return @@ -386,6 +397,8 @@ func generateSourceCodeReviewContent(commentType ReviewCommentType, violation bo return outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent(violation, writer, similarIssues...), writer) case SecretComment: return outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent(violation, writer, similarIssues...), writer) + case ServicesComment: + return outputwriter.GenerateReviewCommentContent(outputwriter.ServicesReviewContent(violation, writer, similarIssues...), writer) } return } diff --git a/utils/comment_test.go b/utils/comment_test.go index 10abfa51f..346fca7d5 100644 --- a/utils/comment_test.go +++ b/utils/comment_test.go @@ -252,6 +252,74 @@ func TestGetNewReviewComments(t *testing.T) { }, expectedOutput: []ReviewComment{}, }, + { + name: "Services review comments", + issues: &issues.ScansIssuesCollection{ + ServicesVulnerabilities: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + SeverityNumValue: 13, + }, + Finding: "Exposed service endpoint", + Outcomes: "publicly-exposed", + ScannerInfo: formats.ScannerInfo{ + RuleId: "service-rule", + Cwe: []string{"CWE-918"}, + }, + Location: formats.Location{ + File: "service.yaml", + StartLine: 3, + StartColumn: 4, + EndLine: 5, + EndColumn: 6, + Snippet: "port: 8080", + }, + }, + }, + }, + expectedOutput: []ReviewComment{ + { + Location: formats.Location{ + File: "service.yaml", + StartLine: 3, + StartColumn: 4, + EndLine: 5, + EndColumn: 6, + Snippet: "port: 8080", + }, + Type: ServicesComment, + CommentInfo: vcsclient.PullRequestComment{ + CommentInfo: vcsclient.CommentInfo{ + Content: outputwriter.GenerateReviewCommentContent(outputwriter.ServicesReviewContent(false, writer, formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{ + Severity: "High", + SeverityNumValue: 13, + }, + Finding: "Exposed service endpoint", + Outcomes: "publicly-exposed", + ScannerInfo: formats.ScannerInfo{ + RuleId: "service-rule", + Cwe: []string{"CWE-918"}, + }, + }), writer), + }, + PullRequestDiff: vcsclient.PullRequestDiff{ + OriginalFilePath: "service.yaml", + OriginalStartLine: 3, + OriginalStartColumn: 4, + OriginalEndLine: 5, + OriginalEndColumn: 6, + NewFilePath: "service.yaml", + NewStartLine: 3, + NewStartColumn: 4, + NewEndLine: 5, + NewEndColumn: 6, + }, + }, + }, + }, + }, { name: "Secret review comments", generateSecretsComments: true, diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index e4df23583..f41f86cb2 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -28,6 +28,9 @@ type ScansIssuesCollection struct { SecretsVulnerabilities []formats.SourceCodeRow SecretsViolations []formats.SourceCodeRow + ServicesVulnerabilities []formats.SourceCodeRow + ServicesViolations []formats.SourceCodeRow + SastViolations []formats.SourceCodeRow SastVulnerabilities []formats.SourceCodeRow } @@ -57,6 +60,13 @@ func (ic *ScansIssuesCollection) Append(issues *ScansIssuesCollection) { if len(issues.SecretsViolations) > 0 { ic.SecretsViolations = append(ic.SecretsViolations, issues.SecretsViolations...) } + // Services + if len(issues.ServicesVulnerabilities) > 0 { + ic.ServicesVulnerabilities = append(ic.ServicesVulnerabilities, issues.ServicesVulnerabilities...) + } + if len(issues.ServicesViolations) > 0 { + ic.ServicesViolations = append(ic.ServicesViolations, issues.ServicesViolations...) + } // Sast if len(issues.SastVulnerabilities) > 0 { ic.SastVulnerabilities = append(ic.SastVulnerabilities, issues.SastVulnerabilities...) @@ -83,6 +93,9 @@ func (ic *ScansIssuesCollection) AppendStatus(scanStatus formats.ScanStatus) { if ic.SecretsStatusCode == nil || (*ic.SecretsStatusCode == 0 && scanStatus.SecretsStatusCode != nil) { ic.SecretsStatusCode = scanStatus.SecretsStatusCode } + if ic.ServicesStatusCode == nil || (*ic.ServicesStatusCode == 0 && scanStatus.ServicesStatusCode != nil) { + ic.ServicesStatusCode = scanStatus.ServicesStatusCode + } if ic.SastStatusCode == nil || (*ic.SastStatusCode == 0 && scanStatus.SastStatusCode != nil) { ic.SastStatusCode = scanStatus.SastStatusCode } @@ -105,6 +118,8 @@ func (ic *ScansIssuesCollection) GetScanStatus(scanType utils.SubScanType) *int return ic.IacStatusCode case utils.SecretsScan: return ic.SecretsStatusCode + case utils.ServicesScan: + return ic.ServicesStatusCode case utils.SastScan: return ic.SastStatusCode case utils.ContextualAnalysisScan: @@ -127,6 +142,9 @@ func (ic *ScansIssuesCollection) HasErrors() bool { if secretsStatus := ic.GetScanStatus(utils.SecretsScan); secretsStatus != nil && *secretsStatus != 0 { return true } + if servicesStatus := ic.GetScanStatus(utils.ServicesScan); servicesStatus != nil && *servicesStatus != 0 { + return true + } if sastStatus := ic.GetScanStatus(utils.SastScan); sastStatus != nil && *sastStatus != 0 { return true } @@ -171,6 +189,13 @@ func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubSc if vulnerabilities { jasVulnerabilities = ic.SecretsVulnerabilities } + case utils.ServicesScan: + if isViolation { + jasViolations = ic.ServicesViolations + } + if vulnerabilities { + jasVulnerabilities = ic.ServicesVulnerabilities + } case utils.SastScan: // Count Sast issues only if requested if isViolation { @@ -191,7 +216,8 @@ func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubSc } func (ic *ScansIssuesCollection) IssuesExists(includeSecrets bool) bool { - return ic.ScaIssuesExists() || ic.IacIssuesExists() || ic.SastIssuesExists() || (includeSecrets && ic.SecretsIssuesExists()) + return ic.ScaIssuesExists() || ic.IacIssuesExists() || ic.SastIssuesExists() || ic.ServicesIssuesExists() || + (includeSecrets && ic.SecretsIssuesExists()) } func (ic *ScansIssuesCollection) ScaIssuesExists() bool { @@ -206,6 +232,10 @@ func (ic *ScansIssuesCollection) SecretsIssuesExists() bool { return len(ic.SecretsVulnerabilities) > 0 || len(ic.SecretsViolations) > 0 } +func (ic *ScansIssuesCollection) ServicesIssuesExists() bool { + return len(ic.ServicesVulnerabilities) > 0 || len(ic.ServicesViolations) > 0 +} + func (ic *ScansIssuesCollection) SastIssuesExists() bool { return len(ic.SastVulnerabilities) > 0 || len(ic.SastViolations) > 0 } @@ -242,6 +272,12 @@ func (ic *ScansIssuesCollection) IsFailPrRuleApplied() bool { return true } } + for _, servicesViolation := range ic.ServicesViolations { + if servicesViolation.FailPr { + log.Debug(fmt.Sprintf(FailPrRuleMessage, servicesViolation.ViolationContext.Watch)) + return true + } + } return false } @@ -321,7 +357,7 @@ func (ic *ScansIssuesCollection) GetApplicableEvidences() (evidences []Applicabl // Violations func (ic *ScansIssuesCollection) GetTotalViolations(includeSecrets bool) int { - total := ic.GetTotalScaViolations() + len(ic.IacViolations) + len(ic.SastViolations) + total := ic.GetTotalScaViolations() + len(ic.IacViolations) + len(ic.SastViolations) + len(ic.ServicesViolations) if includeSecrets { total += len(ic.SecretsViolations) } @@ -335,7 +371,7 @@ func (ic *ScansIssuesCollection) GetTotalScaViolations() int { // Vulnerabilities func (ic *ScansIssuesCollection) GetTotalVulnerabilities(includeSecrets bool) int { - total := len(ic.ScaVulnerabilities) + len(ic.IacVulnerabilities) + len(ic.SastVulnerabilities) + total := len(ic.ScaVulnerabilities) + len(ic.IacVulnerabilities) + len(ic.SastVulnerabilities) + len(ic.ServicesVulnerabilities) if includeSecrets { total += len(ic.SecretsVulnerabilities) } diff --git a/utils/issues/issuescollection_test.go b/utils/issues/issuescollection_test.go index ab9a857e5..113f4a3c0 100644 --- a/utils/issues/issuescollection_test.go +++ b/utils/issues/issuescollection_test.go @@ -145,10 +145,24 @@ func getTestData() ScansIssuesCollection { SeverityDetails: formats.SeverityDetails{Severity: "High"}, }, }, + ServicesVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "Medium"}}}, + ServicesViolations: []formats.SourceCodeRow{{ + SeverityDetails: formats.SeverityDetails{Severity: "Low"}, + ViolationContext: formats.ViolationContext{ + IssueId: "services-violation-id", + Watch: "services-watch", + }, + }}, } return issuesCollection } +func getServicesOnlyTestData() ScansIssuesCollection { + return ScansIssuesCollection{ + ServicesVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "High"}}}, + } +} + func TestGetAllIssuesCount(t *testing.T) { testCases := []struct { name string @@ -158,12 +172,12 @@ func TestGetAllIssuesCount(t *testing.T) { { name: "With Secrets", includeSecrets: true, - expectedFindings: 9, + expectedFindings: 11, }, { name: "No Secrets", includeSecrets: false, - expectedFindings: 7, + expectedFindings: 9, }, } issuesCollection := getTestData() @@ -184,12 +198,12 @@ func TestGetTotalVulnerabilities(t *testing.T) { { name: "With Secrets", includeSecrets: true, - expectedFindings: 6, + expectedFindings: 7, }, { name: "No Secrets", includeSecrets: false, - expectedFindings: 5, + expectedFindings: 6, }, } issuesCollection := getTestData() @@ -210,12 +224,12 @@ func TestGetTotalViolations(t *testing.T) { { name: "With Secrets", includeSecrets: true, - expectedFindings: 3, + expectedFindings: 4, }, { name: "No Secrets", includeSecrets: false, - expectedFindings: 2, + expectedFindings: 3, }, } issuesCollection := getTestData() @@ -292,6 +306,25 @@ func TestGetScanIssuesSeverityCount(t *testing.T) { violation: true, expectedSeverityCount: map[string]int{"High": 2}, }, + { + name: "Services Vulnerabilities", + scanType: utils.ServicesScan, + vulnerabilities: true, + expectedSeverityCount: map[string]int{"Medium": 1}, + }, + { + name: "Services Violations", + scanType: utils.ServicesScan, + violation: true, + expectedSeverityCount: map[string]int{"Low": 1}, + }, + { + name: "Services Vulnerabilities and Violations", + scanType: utils.ServicesScan, + vulnerabilities: true, + violation: true, + expectedSeverityCount: map[string]int{"Medium": 1, "Low": 1}, + }, { name: "Sast Vulnerabilities", scanType: utils.SastScan, @@ -378,6 +411,16 @@ func TestIssuesExists(t *testing.T) { includeSecrets: true, expected: true, }, + { + name: "With Services only", + issues: getServicesOnlyTestData(), + expected: true, + }, + { + name: "With Services only and secrets excluded", + issues: getServicesOnlyTestData(), + expected: true, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -416,6 +459,12 @@ func TestIsFailPrRuleApplied(t *testing.T) { scannerToAddFailPrRule: "secrets", expected: true, }, + { + name: "Services violations with fail PR rule", + issues: getTestData(), + scannerToAddFailPrRule: "services", + expected: true, + }, { name: "Sast violations with fail PR rule", issues: getTestData(), @@ -439,6 +488,8 @@ func TestIsFailPrRuleApplied(t *testing.T) { tc.issues.LicensesViolations[0].FailPr = true case "secrets": tc.issues.SecretsViolations[0].FailPr = true + case "services": + tc.issues.ServicesViolations[0].FailPr = true case "sast": tc.issues.SastViolations = append(tc.issues.SastViolations, formats.SourceCodeRow{ ViolationContext: formats.ViolationContext{ @@ -490,11 +541,19 @@ func TestHasErrors(t *testing.T) { ScaStatusCode: utils.NewIntPtr(-1), SastStatusCode: utils.NewIntPtr(0), SecretsStatusCode: utils.NewIntPtr(33), + ServicesStatusCode: utils.NewIntPtr(51), IacStatusCode: utils.NewIntPtr(0), ApplicabilityStatusCode: utils.NewIntPtr(0), }, expected: true, }, + { + name: "Services scan failed", + status: formats.ScanStatus{ + ServicesStatusCode: utils.NewIntPtr(33), + }, + expected: true, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -506,9 +565,10 @@ func TestHasErrors(t *testing.T) { func TestIsScanNotCompleted(t *testing.T) { issues := ScansIssuesCollection{ScanStatus: formats.ScanStatus{ - ScaStatusCode: utils.NewIntPtr(-1), - SastStatusCode: utils.NewIntPtr(0), - SecretsStatusCode: utils.NewIntPtr(33), + ScaStatusCode: utils.NewIntPtr(-1), + SastStatusCode: utils.NewIntPtr(0), + SecretsStatusCode: utils.NewIntPtr(33), + ServicesStatusCode: utils.NewIntPtr(51), }} testCases := []struct { name string @@ -534,6 +594,11 @@ func TestIsScanNotCompleted(t *testing.T) { scan: utils.IacScan, expected: true, }, + { + name: "Services scan failed", + scan: utils.ServicesScan, + expected: true, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -552,12 +617,14 @@ func TestAppendStatus(t *testing.T) { SastStatusCode: utils.NewIntPtr(33), ApplicabilityStatusCode: utils.NewIntPtr(0), SecretsStatusCode: utils.NewIntPtr(51), + ServicesStatusCode: utils.NewIntPtr(44), } expectedStatus := formats.ScanStatus{ ScaStatusCode: utils.NewIntPtr(-1), SastStatusCode: utils.NewIntPtr(33), ApplicabilityStatusCode: utils.NewIntPtr(0), SecretsStatusCode: utils.NewIntPtr(51), + ServicesStatusCode: utils.NewIntPtr(44), } issues := ScansIssuesCollection{ScanStatus: oldStatus} issues.AppendStatus(newStatus) diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index bb75a89b8..ddae9cf34 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -20,7 +20,6 @@ const ( FrogbotTitlePrefix = "[🐸 Frogbot]" FrogbotRepoUrl = "https://github.com/jfrog/frogbot" FrogbotDocumentationUrl = "https://jfrog.com/help/r/jfrog-security-user-guide/shift-left-on-security/frogbot" - JfrogSupportUrl = "https://jfrog.com/support/" ReviewCommentId = "FrogbotReviewComment" scanSummaryTitle = "📗 Scan Summary" @@ -35,6 +34,7 @@ const ( //#nosec G101 -- not a secret secretsTitle = "đŸ¤Ģ Secret" + servicesTitle = "🔌 Services" contextualAnalysisTitle = "đŸ“Ļ🔍 Contextual Analysis CVE" iacTitle = "đŸ› ī¸ Infrastructure as Code" sastTitle = "đŸŽ¯ Static Application Security Testing (SAST)" @@ -172,6 +172,7 @@ func ScanSummaryContent(issues issues.ScansIssuesCollection, context results.Res if includeSecrets { secretsDetails = getScanSecurityIssuesDetails(issues, context, utils.SecretsScan, writer) } + servicesDetails := getScanSecurityIssuesDetails(issues, context, utils.ServicesScan, writer) table := NewMarkdownTableWithColumns( NewMarkdownTableSingleValueColumn("Scan Category", "âš ī¸", false), NewMarkdownTableSingleValueColumn("Status", "âš ī¸", true), @@ -181,6 +182,7 @@ func ScanSummaryContent(issues issues.ScansIssuesCollection, context results.Res table.AddRow(MarkAsBold("Contextual Analysis"), getSubScanResultStatus(issues.GetScanStatus(utils.ContextualAnalysisScan)), "") table.AddRow(MarkAsBold("Static Application Security Testing (SAST)"), getSubScanResultStatus(issues.GetScanStatus(utils.SastScan)), getScanSecurityIssuesDetails(issues, context, utils.SastScan, writer)) table.AddRow(MarkAsBold("Secrets"), getSubScanResultStatus(issues.GetScanStatus(utils.SecretsScan)), secretsDetails) + table.AddRow(MarkAsBold("Services"), getSubScanResultStatus(issues.GetScanStatus(utils.ServicesScan)), servicesDetails) table.AddRow(MarkAsBold("Infrastructure as Code (IaC)"), getSubScanResultStatus(issues.GetScanStatus(utils.IacScan)), getScanSecurityIssuesDetails(issues, context, utils.IacScan, writer)) WriteContent(&contentBuilder, table.Build()) return contentBuilder.String() @@ -225,6 +227,8 @@ func getScanSecurityIssuesDetails(issues issues.ScansIssuesCollection, context r severityCountMap = issues.GetScanIssuesSeverityCount(utils.SastScan, countVulnerabilities, countViolations) case utils.SecretsScan: severityCountMap = issues.GetScanIssuesSeverityCount(utils.SecretsScan, countVulnerabilities, countViolations) + case utils.ServicesScan: + severityCountMap = issues.GetScanIssuesSeverityCount(utils.ServicesScan, countVulnerabilities, countViolations) case utils.IacScan: severityCountMap = issues.GetScanIssuesSeverityCount(utils.IacScan, countVulnerabilities, countViolations) } @@ -523,6 +527,16 @@ func SecretReviewContent(violation bool, writer OutputWriter, issues ...formats. return contentBuilder.String() } +func ServicesReviewContent(violation bool, writer OutputWriter, issues ...formats.SourceCodeRow) string { + var contentBuilder strings.Builder + WriteContent(&contentBuilder, + writer.MarkAsTitle(fmt.Sprintf("%s %s", servicesTitle, getIssueType(violation)), 2), + writer.MarkInCenter(getServicesDescriptionTable(writer, issues...)), + getJasFullDescription(violation, writer, getServicesRuleFullDescriptionTable, issues...), + ) + return contentBuilder.String() +} + func SnippetReviewContent( violation bool, writer OutputWriter, @@ -597,6 +611,36 @@ func getSecretsRuleFullDescriptionTable(info formats.ScannerInfo, writer OutputW return table } +func getServicesDescriptionTable(writer OutputWriter, issues ...formats.SourceCodeRow) string { + table := NewMarkdownTable("Severity", "ID", "CWE", "Outcomes", "Finding", "Watch Name", "Policies").SetDelimiter(writer.Separator()) + table.GetColumnInfo("ID").OmitEmpty = true + table.GetColumnInfo("CWE").OmitEmpty = true + table.GetColumnInfo("Outcomes").OmitEmpty = true + table.GetColumnInfo("Watch Name").OmitEmpty = true + table.GetColumnInfo("Policies").OmitEmpty = true + for _, issue := range issues { + table.AddRowWithCellData( + NewCellData(writer.FormattedSeverity(issue.Severity, jasutils.Applicable.String())), + NewCellData(issue.IssueId), + NewCellData(strings.Join(issue.Cwe, ", ")), + NewCellData(issue.Outcomes), + NewCellData(issue.Finding), + NewCellData(issue.Watch), + NewCellData(issue.Policies...), + ) + } + return table.Build() +} + +func getServicesRuleFullDescriptionTable(info formats.ScannerInfo, writer OutputWriter) *MarkdownTableBuilder { + table := getBaseJasDetailsTable(info, writer) + table.AddRow(MarkAsBold("Rule ID:"), info.RuleId) + if len(info.Cwe) > 0 { + table.AddRow(MarkAsBold("CWE:"), strings.Join(info.Cwe, ", ")) + } + return table +} + // Utilities func getIssueType(violation bool) string { diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go index 417249b3e..2c14edc71 100644 --- a/utils/outputwriter/outputcontent_test.go +++ b/utils/outputwriter/outputcontent_test.go @@ -898,6 +898,120 @@ func TestSnippetReviewContent(t *testing.T) { } } +func TestScanSummaryContentWithServices(t *testing.T) { + testIssues := issues.ScansIssuesCollection{ + ScanStatus: formats.ScanStatus{ + ScaStatusCode: utils.NewIntPtr(0), + ServicesStatusCode: utils.NewIntPtr(0), + }, + ServicesVulnerabilities: []formats.SourceCodeRow{ + {SeverityDetails: formats.SeverityDetails{Severity: "High"}}, + {SeverityDetails: formats.SeverityDetails{Severity: "Medium"}}, + }, + } + context := results.ResultContext{IncludeVulnerabilities: true} + testCases := []struct { + name string + writer OutputWriter + }{ + {name: "Standard output", writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}}, + {name: "Simplified output", writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + output := ScanSummaryContent(testIssues, context, false, tc.writer) + assert.Contains(t, output, "**Services**") + assert.Contains(t, output, "✅ Done") + assert.Contains(t, output, "2 Issues Found") + assert.Contains(t, output, "Frogbot scanned for vulnerabilities and found 2 issues") + }) + } +} + +func TestServicesReviewContent(t *testing.T) { + testCases := []struct { + name string + issues []formats.SourceCodeRow + cases []OutputTestCase + }{ + { + name: "Services review comment content", + issues: []formats.SourceCodeRow{{ + SeverityDetails: formats.SeverityDetails{Severity: "High"}, + Finding: "Exposed service endpoint", + Outcomes: "publicly-exposed", + ScannerInfo: formats.ScannerInfo{ + RuleId: "service-rule", + Cwe: []string{"CWE-918"}, + ScannerDescription: "Services scanner description", + ScannerShortDescription: "Short description", + Origin: "JFrog", + }, + }}, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "services", "services_review_content_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "services", "services_review_content_simplified.md")}, + }, + }, + }, + { + name: "Services violation review comment content", + issues: []formats.SourceCodeRow{{ + SeverityDetails: formats.SeverityDetails{Severity: "Critical"}, + Finding: "Misconfigured service port", + Outcomes: "misconfigured-port", + ScannerInfo: formats.ScannerInfo{ + RuleId: "service-rule", + Cwe: []string{"CWE-200"}, + ScannerDescription: "Services scanner description", + ScannerShortDescription: "Short description", + Origin: "JFrog", + }, + ViolationContext: formats.ViolationContext{ + Watch: "services-watch", + IssueId: "services-violation-id", + Policies: []string{"policy1"}, + }, + }}, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "services", "services_violation_review_content_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "services", "services_violation_review_content_simplified.md")}, + }, + }, + }, + } + + for _, tc := range testCases { + for _, test := range tc.cases { + t.Run(tc.name+"_"+test.name, func(t *testing.T) { + expectedOutput := GetExpectedTestOutput(t, test) + violations := false + for _, issue := range tc.issues { + if issue.Watch != "" { + violations = true + break + } + } + assert.Equal(t, expectedOutput, ServicesReviewContent(violations, test.writer, tc.issues...)) + }) + } + } +} + func TestSecretsReviewContent(t *testing.T) { testCases := []struct { name string diff --git a/utils/scandetails.go b/utils/scandetails.go index 8230d6d58..490eafde3 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -59,7 +59,7 @@ func (sc *ScanDetails) SetResultsToCompare(results *results.SecurityCommandResul func (sc *ScanDetails) SetResultsContext(httpCloneUrl string, jfrogProjectKey string, includeVulnerabilities bool) *ScanDetails { includeSnippetDetection := strings.ToLower(os.Getenv(plugin.SnippetDetectionEnvVariable)) == "true" - sc.ResultContext = audit.CreateAuditResultsContext(sc.ServerDetails, sc.XrayVersion, []string{}, sc.RepoPath, jfrogProjectKey, httpCloneUrl, includeVulnerabilities, true, false, includeSnippetDetection) + sc.ResultContext = audit.CreateAuditResultsContext(sc.ServerDetails, sc.XrayVersion, []string{}, sc.RepoPath, jfrogProjectKey, httpCloneUrl, includeVulnerabilities, true, false, includeSnippetDetection, false) return sc } diff --git a/utils/utils.go b/utils/utils.go index ec88f6262..fa1e0ccb8 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -340,10 +340,12 @@ func ConvertSarifPathsToRelative(issues *issues.ScansIssuesCollection, workingDi convertSarifPathsInCveApplicability(issues.ScaVulnerabilities, workingDirs...) convertSarifPathsInIacs(issues.IacVulnerabilities, workingDirs...) convertSarifPathsInSecrets(issues.SecretsVulnerabilities, workingDirs...) + convertSarifPathsInServices(issues.ServicesVulnerabilities, workingDirs...) convertSarifPathsInSast(issues.SastVulnerabilities, workingDirs...) convertSarifPathsInCveApplicability(issues.ScaViolations, workingDirs...) convertSarifPathsInIacs(issues.IacViolations, workingDirs...) convertSarifPathsInSecrets(issues.SecretsViolations, workingDirs...) + convertSarifPathsInServices(issues.ServicesViolations, workingDirs...) convertSarifPathsInSast(issues.SastViolations, workingDirs...) } @@ -371,10 +373,18 @@ func convertSarifPathsInIacs(iacs []formats.SourceCodeRow, workingDirs ...string } func convertSarifPathsInSecrets(secrets []formats.SourceCodeRow, workingDirs ...string) { - for i := range secrets { - secret := &secrets[i] + convertSarifPathsInSourceCodeRows(secrets, workingDirs...) +} + +func convertSarifPathsInServices(services []formats.SourceCodeRow, workingDirs ...string) { + convertSarifPathsInSourceCodeRows(services, workingDirs...) +} + +func convertSarifPathsInSourceCodeRows(rows []formats.SourceCodeRow, workingDirs ...string) { + for i := range rows { + row := &rows[i] for _, wd := range workingDirs { - secret.Location.File = utils.GetRelativePath(secret.Location.File, wd) + row.Location.File = utils.GetRelativePath(row.Location.File, wd) } } } From 8fa925bbf1ba15d3daf8fab1e383fa99c2c6e207 Mon Sep 17 00:00:00 2001 From: Or Toren Date: Wed, 3 Jun 2026 13:42:08 +0300 Subject: [PATCH 2/4] go mod tidy --- go.mod | 6 ++++-- go.sum | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index a24337f64..0d729ee10 100644 --- a/go.mod +++ b/go.mod @@ -129,7 +129,8 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security +//orto:missconfiguration-service +replace github.com/jfrog/jfrog-cli-security => github.com/orto17/jfrog-cli-security v0.0.0-20260603084957-decd729f1c04 // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev @@ -137,6 +138,7 @@ replace github.com/jfrog/jfrog-cli-security => ../jfrog-cli-security // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev -replace github.com/jfrog/jfrog-client-go => ../jfrog-client-go +//orto:missconfiguration-service +replace github.com/jfrog/jfrog-client-go => github.com/orto17/jfrog-client-go v0.0.0-20260601083500-656c0e8d801d // replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go master diff --git a/go.sum b/go.sum index 0b01923ac..d0b96ef96 100644 --- a/go.sum +++ b/go.sum @@ -207,6 +207,10 @@ github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/orto17/jfrog-cli-security v0.0.0-20260603084957-decd729f1c04 h1:evZLxNnxYRFcuDO+CrNMOx7G+H7xj9pxFcjlrLAaBWE= +github.com/orto17/jfrog-cli-security v0.0.0-20260603084957-decd729f1c04/go.mod h1:hGz5P+VN0rt7Qoo9lRhjgY3+NCam7hG99BHMJ7Cp8l4= +github.com/orto17/jfrog-client-go v0.0.0-20260601083500-656c0e8d801d h1:rrT1bx+DqF4SrKHtsy0lx/mnSvSsRzYx9DLZhDmGsDM= +github.com/orto17/jfrog-client-go v0.0.0-20260601083500-656c0e8d801d/go.mod h1:FHpjN1nTDoj96xd6obe27EOgGErqzU0rQgC96L3Ch9E= github.com/owenrumney/go-sarif/v3 v3.2.3 h1:n6mdX5ugKwCrZInvBsf6WumXmpAe3mbmQXgkXlIq34U= github.com/owenrumney/go-sarif/v3 v3.2.3/go.mod h1:1bV7t8SZg7pX41spaDkEUs8/yEjzk9JapztMoX1XNjg= github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= From 72f8a679a0449ad9171843f82eb16107279707b8 Mon Sep 17 00:00:00 2001 From: Or Toren Date: Wed, 3 Jun 2026 14:16:30 +0300 Subject: [PATCH 3/4] fixed tests and static analysis --- scanrepository/scanrepository_test.go | 11 ++++++----- utils/getconfiguration.go | 4 ++-- utils/gitlabreport/gitlabreport_test.go | 2 +- utils/testsutils.go | 7 +++++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/scanrepository/scanrepository_test.go b/scanrepository/scanrepository_test.go index 6ed441f31..7be83176b 100644 --- a/scanrepository/scanrepository_test.go +++ b/scanrepository/scanrepository_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - services2 "github.com/jfrog/jfrog-client-go/xsc/services" "io" "net/http" "net/http/httptest" @@ -15,6 +14,8 @@ import ( "strings" "testing" + services2 "github.com/jfrog/jfrog-client-go/xsc/services" + "github.com/CycloneDX/cyclonedx-go" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/protocol/packp" @@ -499,7 +500,7 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { ResultsMetaData: results.ResultsMetaData{ ResultContext: results.ResultContext{IncludeVulnerabilities: true}}, Targets: []*results.TargetResults{{ - ScanTarget: results.ScanTarget{Target: "target1", Technologies: []techutils.Technology{techutils.Npm}}, + ScanTarget: results.ScanTarget{Target: "target1", Technology: techutils.Npm}, ScaResults: &results.ScaScanResults{ Sbom: loadTestSBOM(t, "sbom_with_vulnerabilities.json"), }, @@ -521,7 +522,7 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { ResultsMetaData: results.ResultsMetaData{ ResultContext: results.ResultContext{IncludeVulnerabilities: true}}, Targets: []*results.TargetResults{{ - ScanTarget: results.ScanTarget{Target: "target1", Technologies: []techutils.Technology{techutils.Npm}}, + ScanTarget: results.ScanTarget{Target: "target1", Technology: techutils.Npm}, ScaResults: &results.ScaScanResults{ Sbom: loadTestSBOM(t, "sbom_multiple_vulns_same_pkg.json"), }, @@ -541,7 +542,7 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { ResultsMetaData: results.ResultsMetaData{ ResultContext: results.ResultContext{IncludeVulnerabilities: true}}, Targets: []*results.TargetResults{{ - ScanTarget: results.ScanTarget{Target: "target1", Technologies: []techutils.Technology{techutils.Npm}}, + ScanTarget: results.ScanTarget{Target: "target1", Technology: techutils.Npm}, ScaResults: &results.ScaScanResults{ Sbom: loadTestSBOM(t, "sbom_no_fix_version.json"), }, @@ -561,7 +562,7 @@ func TestCreateVulnerabilitiesMap(t *testing.T) { ResultsMetaData: results.ResultsMetaData{ ResultContext: results.ResultContext{Watches: []string{"w1"}}}, Targets: []*results.TargetResults{{ - ScanTarget: results.ScanTarget{Target: "target1", Technologies: []techutils.Technology{techutils.Npm}}, + ScanTarget: results.ScanTarget{Target: "target1", Technology: techutils.Npm}, }}, Violations: &violationutils.Violations{ Sca: []violationutils.CveViolation{ diff --git a/utils/getconfiguration.go b/utils/getconfiguration.go index f9649dc9b..b400ea0a7 100644 --- a/utils/getconfiguration.go +++ b/utils/getconfiguration.go @@ -506,7 +506,7 @@ func getBoolEnv(envKey string, defaultValue bool) (bool, error) { return defaultValue, nil } -func getConfigurationProfile(xrayVersion string, jfrogServer *coreconfig.ServerDetails, gitClient vcsclient.VcsClient, gitParams *Git, projectKey string) (configProfile *services.ConfigProfile, repoCloneUrl string, err error) { +func getConfigurationProfile(xrayVersion string, jfrogServer *coreconfig.ServerDetails, gitClient vcsclient.VcsClient, gitParams *Git, _ string) (configProfile *services.ConfigProfile, repoCloneUrl string, err error) { if err = clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, configProfileV3MinXrayVersion); err != nil { log.Info(fmt.Sprintf("The utilized Frogbot version requires a higher version of Xray than %s in order to use Config Profile. Please upgrade Xray to version %s and above. Frogbot configurations will be derived from environment variables only.", xrayVersion, configProfileV3MinXrayVersion)) return @@ -515,7 +515,7 @@ func getConfigurationProfile(xrayVersion string, jfrogServer *coreconfig.ServerD return } log.Debug(fmt.Sprintf("Searching central configuration associated to repository '%s'", jfrogServer.Url)) - if configProfile, err = xsc.GetConfigProfileByUrl(xrayVersion, jfrogServer, repoCloneUrl, projectKey); err != nil || configProfile == nil { + if configProfile, err = xsc.GetConfigProfileByUrl(xrayVersion, jfrogServer, repoCloneUrl); err != nil || configProfile == nil { return } diff --git a/utils/gitlabreport/gitlabreport_test.go b/utils/gitlabreport/gitlabreport_test.go index 08672fbdf..e64f4da7f 100644 --- a/utils/gitlabreport/gitlabreport_test.go +++ b/utils/gitlabreport/gitlabreport_test.go @@ -307,7 +307,7 @@ func TestConvertToGitLabDependencyScanningReport(t *testing.T) { t.Run("failure status when GetErrors returns error", func(t *testing.T) { sr := scanResultsWithSbomOnly() - sr.GeneralErrors = []results.SkippableError{{ActualError: errors.New("scanner failed")}} + sr.GeneralError = errors.New("scanner failed") report, err := ConvertToGitLabDependencyScanningReport(sr, start, end, version) require.NoError(t, err) assert.Equal(t, "failure", report.Scan.Status) diff --git a/utils/testsutils.go b/utils/testsutils.go index 719f7730f..d2c0caa1c 100644 --- a/utils/testsutils.go +++ b/utils/testsutils.go @@ -100,6 +100,13 @@ func ChangeToTempDirWithCallback(t *testing.T) (string, func() error) { return tmpDir, callback } +// SkipIfNoJFrogEnv skips integration tests when JFrog platform credentials are not configured. +func SkipIfNoJFrogEnv(t *testing.T) { + if strings.TrimSuffix(os.Getenv(JFrogUrlEnv), "/") == "" { + t.Skipf("skipping: %s is not set (integration tests run in CI with platform credentials)", JFrogUrlEnv) + } +} + // Check connection details with JFrog instance. // Return a callback method that restores the credentials after the test is done. func VerifyEnv(t *testing.T) (server config.ServerDetails, restoreFunc func()) { From bd0f70a2e933ea097b4e82cf59db5763f4ed941e Mon Sep 17 00:00:00 2001 From: Or Toren Date: Tue, 16 Jun 2026 13:24:00 +0300 Subject: [PATCH 4/4] go mod tidy --- go.mod | 2 +- go.sum | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0d729ee10..6a0d5ff4c 100644 --- a/go.mod +++ b/go.mod @@ -130,7 +130,7 @@ require ( ) //orto:missconfiguration-service -replace github.com/jfrog/jfrog-cli-security => github.com/orto17/jfrog-cli-security v0.0.0-20260603084957-decd729f1c04 +replace github.com/jfrog/jfrog-cli-security => github.com/orto17/jfrog-cli-security v0.0.0-20260616102058-ea6b4c628c96 // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev diff --git a/go.sum b/go.sum index d0b96ef96..5d71fe901 100644 --- a/go.sum +++ b/go.sum @@ -207,8 +207,9 @@ github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= -github.com/orto17/jfrog-cli-security v0.0.0-20260603084957-decd729f1c04 h1:evZLxNnxYRFcuDO+CrNMOx7G+H7xj9pxFcjlrLAaBWE= -github.com/orto17/jfrog-cli-security v0.0.0-20260603084957-decd729f1c04/go.mod h1:hGz5P+VN0rt7Qoo9lRhjgY3+NCam7hG99BHMJ7Cp8l4= +github.com/orto17/jfrog-cli-security v0.0.0-20260616085217-c971fe434253 h1:WuiE3pYkkZKWNJ7+HZJTAXezS6snVAnmrITCWa3kqJM= +github.com/orto17/jfrog-cli-security v0.0.0-20260616085217-c971fe434253/go.mod h1:hGz5P+VN0rt7Qoo9lRhjgY3+NCam7hG99BHMJ7Cp8l4= +github.com/orto17/jfrog-cli-security v0.0.0-20260616102058-ea6b4c628c96/go.mod h1:hGz5P+VN0rt7Qoo9lRhjgY3+NCam7hG99BHMJ7Cp8l4= github.com/orto17/jfrog-client-go v0.0.0-20260601083500-656c0e8d801d h1:rrT1bx+DqF4SrKHtsy0lx/mnSvSsRzYx9DLZhDmGsDM= github.com/orto17/jfrog-client-go v0.0.0-20260601083500-656c0e8d801d/go.mod h1:FHpjN1nTDoj96xd6obe27EOgGErqzU0rQgC96L3Ch9E= github.com/owenrumney/go-sarif/v3 v3.2.3 h1:n6mdX5ugKwCrZInvBsf6WumXmpAe3mbmQXgkXlIq34U=