diff --git a/go.mod b/go.mod
index f364f4b23..6a0d5ff4c 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 => github.com/jfrog/jfrog-cli-security dev
+//orto:missconfiguration-service
+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
@@ -137,6 +138,7 @@ 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
+//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 fbf77ca41..5d71fe901 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=
@@ -211,6 +207,11 @@ 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-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=
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=
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/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/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 | 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 | 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/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/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/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()) {
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)
}
}
}