diff --git a/go.mod b/go.mod
index 43f2c0981..b2fa2d6b7 100644
--- a/go.mod
+++ b/go.mod
@@ -130,7 +130,8 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
)
-// replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security dev
+// attiasas:services-sub-scan-source-code
+replace github.com/jfrog/jfrog-cli-security => github.com/attiasas/jfrog-cli-security v0.0.0-20260806060325-9a4c41b4285b
// replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev
@@ -138,6 +139,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
+// orto17:missconfiguration-service
+replace github.com/jfrog/jfrog-client-go => github.com/orto17/jfrog-client-go v0.0.0-20260803124813-bb92d09fecf2
// replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go master
diff --git a/go.sum b/go.sum
index 06d717445..bd316c64e 100644
--- a/go.sum
+++ b/go.sum
@@ -21,6 +21,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/attiasas/jfrog-cli-security v0.0.0-20260806060325-9a4c41b4285b h1:6c8YDC2VuDY8c90dZ2gCz9Vt9apJUz+mRqCgynJAKNE=
+github.com/attiasas/jfrog-cli-security v0.0.0-20260806060325-9a4c41b4285b/go.mod h1:YhlMkzTmOlKl9tgm2l9ye22mt6S7eBv5DU4g3Tu5y1M=
github.com/beevik/etree v1.7.0 h1:xjBk9O4p4x7D1YajePjfLzdaFC4/uYUENA7P0pv6gXA=
github.com/beevik/etree v1.7.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
@@ -154,10 +156,6 @@ github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260803134309-cea6be60a1f2 h1:I
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260803134309-cea6be60a1f2/go.mod h1:1vxzqW7jHBSuTNqO2vxEnhbniwq4dj5wveDuCMJX7Yo=
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260728123939-34b27f070f2e h1:K0IK3w5a5h6SIi9yoOJ6a7DL+kuFjs5acypOxKyT2OM=
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260728123939-34b27f070f2e/go.mod h1:MygQx8pekgPCXyXnejIAVG9S4ImGcDFmcfRPUug/0d0=
-github.com/jfrog/jfrog-cli-security v1.32.1 h1:GQ89waCbRZSL6fEpFwGWpIeiy2c8SNUTzccyec2QC1Y=
-github.com/jfrog/jfrog-cli-security v1.32.1/go.mod h1:BKhIR0RkW0KMIdAeJbufG4rl44wZbpn+5bfE2jEN338=
-github.com/jfrog/jfrog-client-go v1.55.1-0.20260803094922-a87c05639195 h1:1h6qSM8fanMsy9xT7zrRn/lrAMclzQzT5OJt4a9X4Qo=
-github.com/jfrog/jfrog-client-go v1.55.1-0.20260803094922-a87c05639195/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=
@@ -215,6 +213,8 @@ 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-client-go v0.0.0-20260803124813-bb92d09fecf2 h1:CVSdxDuYR3F7WXTXO4Pn1eSFRKE3X/bKeHhvGL+xhlY=
+github.com/orto17/jfrog-client-go v0.0.0-20260803124813-bb92d09fecf2/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 90a156fe1..af7c4c40c 100644
--- a/scanpullrequest/scanpullrequest.go
+++ b/scanpullrequest/scanpullrequest.go
@@ -195,6 +195,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)
}
@@ -204,6 +205,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)
}
@@ -213,6 +215,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)
}
@@ -256,6 +259,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))
@@ -354,6 +363,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
@@ -445,6 +458,9 @@ func scanResultsToIssuesCollection(scanResults *results.SecurityCommandResults,
IacVulnerabilities: simpleJsonResults.IacsVulnerabilities,
IacViolations: simpleJsonResults.IacsViolations,
+ ServicesVulnerabilities: simpleJsonResults.ServicesVulnerabilities,
+ ServicesViolations: simpleJsonResults.ServicesViolations,
+
SecretsVulnerabilities: simpleJsonResults.SecretsVulnerabilities,
SecretsViolations: simpleJsonResults.SecretsViolations,
@@ -484,6 +500,7 @@ func getScanStatus(cmdResults ...formats.SimpleJsonResults) formats.ScanStatus {
for _, sourceResults := range cmdResults[1:] {
statuses.ScaStatusCode = getWorstScanStatus(statuses.ScaStatusCode, sourceResults.Statuses.ScaStatusCode)
statuses.IacStatusCode = getWorstScanStatus(statuses.IacStatusCode, sourceResults.Statuses.IacStatusCode)
+ statuses.ServicesStatusCode = getWorstScanStatus(statuses.ServicesStatusCode, sourceResults.Statuses.ServicesStatusCode)
statuses.SecretsStatusCode = getWorstScanStatus(statuses.SecretsStatusCode, sourceResults.Statuses.SecretsStatusCode)
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 e79fbfbc4..faf5288dc 100644
--- a/scanpullrequest/scanpullrequest_test.go
+++ b/scanpullrequest/scanpullrequest_test.go
@@ -75,6 +75,7 @@ func TestScanResultsToIssuesCollection(t *testing.T) {
ScaScanStatusCode: securityutils.NewIntPtr(0),
ContextualAnalysisStatusCode: securityutils.NewIntPtr(0),
IacScanStatusCode: securityutils.NewIntPtr(0),
+ ServicesScanStatusCode: securityutils.NewIntPtr(0),
SecretsScanStatusCode: securityutils.NewIntPtr(0),
SastScanStatusCode: securityutils.NewIntPtr(0),
},
@@ -107,6 +108,13 @@ func TestScanResultsToIssuesCollection(t *testing.T) {
),
),
},
+ ServicesScanResults: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResults(
+ sarifutils.CreateResultWithLocations("Exposed service endpoint detected", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.Medium).String(),
+ sarifutils.CreateLocation("service.yaml", 1, 10, 2, 11, "services-snippet"),
+ ),
+ ),
+ },
SecretsScanResults: []*sarif.Run{
sarifutils.CreateRunWithDummyResults(
sarifutils.CreateResultWithLocations("Secret", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.High).String(),
@@ -166,6 +174,27 @@ func TestScanResultsToIssuesCollection(t *testing.T) {
},
},
},
+ ServicesVulnerabilities: []formats.SourceCodeRow{
+ {
+ SeverityDetails: formats.SeverityDetails{
+ Severity: "Medium",
+ SeverityNumValue: 26,
+ },
+ ScannerInfo: formats.ScannerInfo{
+ ScannerDescription: "rule-msg",
+ RuleId: "rule",
+ },
+ Finding: "Exposed service endpoint detected",
+ Location: formats.Location{
+ File: "service.yaml",
+ StartLine: 1,
+ StartColumn: 10,
+ EndLine: 2,
+ EndColumn: 11,
+ Snippet: "services-snippet",
+ },
+ },
+ },
SecretsVulnerabilities: []formats.SourceCodeRow{
{
SeverityDetails: formats.SeverityDetails{
@@ -215,6 +244,7 @@ func TestScanResultsToIssuesCollection(t *testing.T) {
if assert.NoError(t, err) {
assert.ElementsMatch(t, expectedOutput.ScaVulnerabilities, issuesRows.ScaVulnerabilities)
assert.ElementsMatch(t, expectedOutput.IacVulnerabilities, issuesRows.IacVulnerabilities)
+ assert.ElementsMatch(t, expectedOutput.ServicesVulnerabilities, issuesRows.ServicesVulnerabilities)
assert.ElementsMatch(t, expectedOutput.SecretsVulnerabilities, issuesRows.SecretsVulnerabilities)
assert.ElementsMatch(t, expectedOutput.SastVulnerabilities, issuesRows.SastVulnerabilities)
assert.ElementsMatch(t, expectedOutput.LicensesViolations, issuesRows.LicensesViolations)
@@ -584,6 +614,31 @@ func TestFilterJasResultsIfScanFailed(t *testing.T) {
},
hasFailure: true,
},
+ {
+ name: "Services scanner failed - should remove Services vulnerabilities",
+ cmdStep: results.CmdStepServices,
+ targetResult: &results.TargetResults{
+ JasResults: &results.JasScansResults{
+ JasVulnerabilities: results.JasScanResults{
+ ServicesScanResults: []*sarif.Run{},
+ },
+ },
+ ResultsStatus: results.ResultsStatus{
+ ServicesScanStatusCode: intPtr(0),
+ },
+ },
+ sourceResult: &results.TargetResults{
+ JasResults: &results.JasScansResults{
+ JasVulnerabilities: results.JasScanResults{
+ ServicesScanResults: []*sarif.Run{},
+ },
+ },
+ ResultsStatus: results.ResultsStatus{
+ ServicesScanStatusCode: intPtr(1),
+ },
+ },
+ hasFailure: true,
+ },
{
name: "SAST scanner failed - should remove SAST vulnerabilities",
cmdStep: results.CmdStepSast,
@@ -699,6 +754,8 @@ func TestFilterJasResultsIfScanFailed(t *testing.T) {
assert.Nil(t, test.sourceResult.JasResults.JasVulnerabilities.SecretsScanResults, "Secrets vulnerability scan results should be removed when scan failed")
case results.CmdStepIaC:
assert.Nil(t, test.sourceResult.JasResults.JasVulnerabilities.IacScanResults, "IaC vulnerability scan results should be removed when scan failed")
+ case results.CmdStepServices:
+ assert.Nil(t, test.sourceResult.JasResults.JasVulnerabilities.ServicesScanResults, "Services vulnerability scan results should be removed when scan failed")
case results.CmdStepSast:
assert.Nil(t, test.sourceResult.JasResults.JasVulnerabilities.SastScanResults, "SAST vulnerability scan results should be removed when scan failed")
}
@@ -1138,6 +1195,7 @@ func TestFilterViolationsResults(t *testing.T) {
shouldRemoveSca bool
shouldRemoveSecrets bool
shouldRemoveIac bool
+ shouldRemoveServices bool
shouldRemoveSast bool
shouldRemoveAllViolations bool
}{
@@ -1171,6 +1229,20 @@ func TestFilterViolationsResults(t *testing.T) {
targetResults: createSecurityCommandResultsForTest("test-target", "", false, false, false, false, false, false, 0, 0, 0, 1, 0, 0),
shouldRemoveIac: true,
},
+ {
+ name: "Violations scan succeeded, Services scan failed in source - should remove only Services violations",
+ sourceResults: func() *results.SecurityCommandResults {
+ result := createSecurityCommandResultsForTest("test-target", "", false, false, false, false, false, true, 0, 0, 0, 0, 0, 0)
+ result.Targets[0].ResultsStatus.ServicesScanStatusCode = intPtr(1)
+ return result
+ }(),
+ targetResults: func() *results.SecurityCommandResults {
+ result := createSecurityCommandResultsForTest("test-target", "", false, false, false, false, false, false, 0, 0, 0, 0, 0, 0)
+ result.Targets[0].ResultsStatus.ServicesScanStatusCode = intPtr(0)
+ return result
+ }(),
+ shouldRemoveServices: true,
+ },
{
name: "Violations scan succeeded, SAST scan failed in source - should remove only SAST violations",
sourceResults: createSecurityCommandResultsForTest("test-target", "", false, false, false, false, false, true, 0, 0, 0, 0, 1, 0),
@@ -1230,6 +1302,12 @@ func TestFilterViolationsResults(t *testing.T) {
assert.NotNil(t, test.sourceResults.Violations.Iac, "IaC violations should NOT be removed")
}
+ if test.shouldRemoveServices {
+ assert.Nil(t, test.sourceResults.Violations.Services, "Services violations should be removed")
+ } else {
+ assert.NotNil(t, test.sourceResults.Violations.Services, "Services violations should NOT be removed")
+ }
+
if test.shouldRemoveSast {
assert.Nil(t, test.sourceResults.Violations.Sast, "SAST violations should be removed")
} else {
@@ -1315,6 +1393,21 @@ func TestIsScanFailedInSourceOrTarget(t *testing.T) {
step: results.CmdStepIaC,
expected: true,
},
+ {
+ name: "Services scan failed in source - should return true",
+ sourceResult: &results.TargetResults{
+ ResultsStatus: results.ResultsStatus{
+ ServicesScanStatusCode: intPtr(1),
+ },
+ },
+ targetResult: &results.TargetResults{
+ ResultsStatus: results.ResultsStatus{
+ ServicesScanStatusCode: intPtr(0),
+ },
+ },
+ step: results.CmdStepServices,
+ expected: true,
+ },
{
name: "Both are nil - should return false",
sourceResult: nil,
@@ -1500,10 +1593,11 @@ func createSecurityCommandResultsForTest(targetLocation string, targetName strin
if withViolations {
result.Violations = &violationutils.Violations{
- Sca: []violationutils.CveViolation{{}},
- Secrets: []violationutils.JasViolation{{}},
- Iac: []violationutils.JasViolation{{}},
- Sast: []violationutils.JasViolation{{}},
+ Sca: []violationutils.CveViolation{{}},
+ Secrets: []violationutils.JasViolation{{}},
+ Iac: []violationutils.JasViolation{{}},
+ Services: []violationutils.JasViolation{{}},
+ Sast: []violationutils.JasViolation{{}},
}
}
diff --git a/testdata/messages/integration/test_proj_pip_with_vulnerability.md b/testdata/messages/integration/test_proj_pip_with_vulnerability.md
index 994986d58..236df3c5f 100644
--- a/testdata/messages/integration/test_proj_pip_with_vulnerability.md
+++ b/testdata/messages/integration/test_proj_pip_with_vulnerability.md
@@ -19,6 +19,7 @@
| **Contextual Analysis** | ✅ Done | - |
| **Static Application Security Testing (SAST)** | ✅ Done | Not Found |
| **Secrets** | ✅ Done | - |
+| **Services** | ℹ️ Not Scanned | - |
| **Infrastructure as Code (IaC)** | ✅ Done | Not Found |
### 📦 Vulnerable Dependencies
diff --git a/testdata/messages/integration/test_proj_with_vulnerability_simplified.md b/testdata/messages/integration/test_proj_with_vulnerability_simplified.md
index 18b3e3f64..0385016e7 100644
--- a/testdata/messages/integration/test_proj_with_vulnerability_simplified.md
+++ b/testdata/messages/integration/test_proj_with_vulnerability_simplified.md
@@ -18,6 +18,7 @@
| **Contextual Analysis** | ✅ Done | - |
| **Static Application Security Testing (SAST)** | ✅ Done | Not Found |
| **Secrets** | ✅ Done | - |
+| **Services** | ℹ️ Not Scanned | - |
| **Infrastructure as Code (IaC)** | ✅ Done | Not Found |
diff --git a/testdata/messages/integration/test_proj_with_vulnerability_standard.md b/testdata/messages/integration/test_proj_with_vulnerability_standard.md
index 9b6750fc1..0335e774a 100644
--- a/testdata/messages/integration/test_proj_with_vulnerability_standard.md
+++ b/testdata/messages/integration/test_proj_with_vulnerability_standard.md
@@ -19,6 +19,7 @@
| **Contextual Analysis** | ✅ Done | - |
| **Static Application Security Testing (SAST)** | ✅ Done | Not Found |
| **Secrets** | ✅ Done | - |
+| **Services** | ℹ️ Not Scanned | - |
| **Infrastructure as Code (IaC)** | ✅ Done | Not Found |
### 📦 Vulnerable Dependencies
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..9ee116a11
--- /dev/null
+++ b/testdata/messages/reviewcomment/services/services_review_content_simplified.md
@@ -0,0 +1,28 @@
+
+
+---
+## 🔌 Services Vulnerability
+
+---
+| Severity | Finding |
+| :---------------------: | :-----------------------------------: |
+| Medium | Exposed service endpoint detected |
+
+
+---
+### Full description
+
+---
+
+
+
+---
+### Vulnerability Details
+
+---
+| | |
+| --------------------- | :-----------------------------------: |
+| **Rule ID:** | rule-id |
+
+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..3307a6fb4
--- /dev/null
+++ b/testdata/messages/reviewcomment/services/services_review_content_standard.md
@@ -0,0 +1,21 @@
+
+## 🔌 Services Vulnerability
+
+
+| Severity | Finding |
+| :---------------------: | :-----------------------------------: |
+| 
Medium | Exposed service endpoint detected |
+
+
+
+
+Full description
+
+### Vulnerability Details
+| | |
+| --------------------- | :-----------------------------------: |
+| **Rule ID:** | rule-id |
+
+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..2bc0800b6
--- /dev/null
+++ b/testdata/messages/reviewcomment/services/services_violation_review_content_simplified.md
@@ -0,0 +1,28 @@
+
+
+---
+## 🔌 Services Violation
+
+---
+| Severity | ID | Finding | Watch Name | Policies |
+| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: |
+| Medium | services-violation-id | Exposed service endpoint detected | jas-watch | policy1, policy2 |
+
+
+---
+### Full description
+
+---
+
+
+
+---
+### Violation Details
+
+---
+| | |
+| --------------------- | :-----------------------------------: |
+| **Rule ID:** | rule-id |
+
+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..7491dbc08
--- /dev/null
+++ b/testdata/messages/reviewcomment/services/services_violation_review_content_standard.md
@@ -0,0 +1,21 @@
+
+## 🔌 Services Violation
+
+
+| Severity | ID | Finding | Watch Name | Policies |
+| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: |
+| 
Medium | services-violation-id | Exposed service endpoint detected | jas-watch | policy1
policy2 |
+
+
+
+
+Full description
+
+### Violation Details
+| | |
+| --------------------- | :-----------------------------------: |
+| **Rule ID:** | rule-id |
+
+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/testdata/scanpullrequest/expected_response.md b/testdata/scanpullrequest/expected_response.md
index 1e41a6f8d..f596233bd 100644
--- a/testdata/scanpullrequest/expected_response.md
+++ b/testdata/scanpullrequest/expected_response.md
@@ -19,6 +19,7 @@
| **Contextual Analysis** | ✅ Done | - |
| **Static Application Security Testing (SAST)** | ✅ Done | Not Found |
| **Secrets** | ✅ Done | - |
+| **Services** | ℹ️ Not Scanned | - |
| **Infrastructure as Code (IaC)** | ✅ Done | Not Found |
### 📦 Vulnerable Dependencies
diff --git a/testdata/scanpullrequest/expected_response_multi_dir.md b/testdata/scanpullrequest/expected_response_multi_dir.md
index 8d135ea33..e03dea085 100644
--- a/testdata/scanpullrequest/expected_response_multi_dir.md
+++ b/testdata/scanpullrequest/expected_response_multi_dir.md
@@ -19,6 +19,7 @@
| **Contextual Analysis** | ✅ Done | - |
| **Static Application Security Testing (SAST)** | ✅ Done | Not Found |
| **Secrets** | ✅ Done | - |
+| **Services** | ℹ️ Not Scanned | - |
| **Infrastructure as Code (IaC)** | ✅ Done | Not Found |
### 📦 Vulnerable Dependencies
diff --git a/testdata/scanpullrequest/expected_response_subdir.md b/testdata/scanpullrequest/expected_response_subdir.md
index 22e212c4d..939b48e78 100644
--- a/testdata/scanpullrequest/expected_response_subdir.md
+++ b/testdata/scanpullrequest/expected_response_subdir.md
@@ -19,6 +19,7 @@
| **Contextual Analysis** | ✅ Done | - |
| **Static Application Security Testing (SAST)** | ✅ Done | Not Found |
| **Secrets** | ✅ Done | - |
+| **Services** | ℹ️ Not Scanned | - |
| **Infrastructure as Code (IaC)** | ✅ Done | Not Found |
### 📦 Vulnerable Dependencies
diff --git a/utils/comment.go b/utils/comment.go
index 5faea0216..38eb2a59e 100644
--- a/utils/comment.go
+++ b/utils/comment.go
@@ -32,6 +32,7 @@ const (
ApplicableComment ReviewCommentType = "Applicable"
IacComment ReviewCommentType = "Iac"
SastComment ReviewCommentType = "Sast"
+ ServicesComment ReviewCommentType = "Services"
SecretComment ReviewCommentType = "Secrets"
SnippetComment ReviewCommentType = "Snippet"
@@ -221,7 +222,15 @@ func getNewReviewComments(repo *Repository, issues *issues.ScansIssuesCollection
commentsToAdd = append(commentsToAdd, generateReviewComment(SastComment, similarSastIssues.Location, generateSourceCodeReviewContent(SastComment, true, writer, similarSastIssues.issues...)))
}
}
-
+ // Services review comments
+ for _, services := range issues.ServicesVulnerabilities {
+ commentsToAdd = append(commentsToAdd, generateReviewComment(ServicesComment, services.Location, generateSourceCodeReviewContent(ServicesComment, false, writer, services)))
+ }
+ 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 +395,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 f6c95d943..889774914 100644
--- a/utils/comment_test.go
+++ b/utils/comment_test.go
@@ -573,6 +573,26 @@ func TestGetNewReviewComments(t *testing.T) {
},
},
},
+ ServicesVulnerabilities: []formats.SourceCodeRow{
+ {
+ SeverityDetails: formats.SeverityDetails{
+ Severity: "Medium",
+ SeverityNumValue: 10,
+ },
+ ScannerInfo: formats.ScannerInfo{
+ RuleId: "services-rule",
+ },
+ Finding: "Exposed service endpoint detected",
+ Location: formats.Location{
+ File: "file1",
+ StartLine: 1,
+ StartColumn: 10,
+ EndLine: 2,
+ EndColumn: 11,
+ Snippet: "services-snippet",
+ },
+ },
+ },
SastVulnerabilities: []formats.SourceCodeRow{
{
SeverityDetails: formats.SeverityDetails{
@@ -663,6 +683,43 @@ func TestGetNewReviewComments(t *testing.T) {
},
},
},
+ {
+ Location: formats.Location{
+ File: "file1",
+ StartLine: 1,
+ StartColumn: 10,
+ EndLine: 2,
+ EndColumn: 11,
+ Snippet: "services-snippet",
+ },
+ Type: ServicesComment,
+ CommentInfo: vcsclient.PullRequestComment{
+ CommentInfo: vcsclient.CommentInfo{
+ Content: outputwriter.GenerateReviewCommentContent(outputwriter.ServicesReviewContent(false, writer, formats.SourceCodeRow{
+ SeverityDetails: formats.SeverityDetails{
+ Severity: "Medium",
+ SeverityNumValue: 10,
+ },
+ ScannerInfo: formats.ScannerInfo{
+ RuleId: "services-rule",
+ },
+ Finding: "Exposed service endpoint detected",
+ }), writer),
+ },
+ PullRequestDiff: vcsclient.PullRequestDiff{
+ OriginalFilePath: "file1",
+ OriginalStartLine: 1,
+ OriginalStartColumn: 10,
+ OriginalEndLine: 2,
+ OriginalEndColumn: 11,
+ NewFilePath: "file1",
+ NewStartLine: 1,
+ NewStartColumn: 10,
+ NewEndLine: 2,
+ NewEndColumn: 11,
+ },
+ },
+ },
{
Location: formats.Location{
File: "file1",
diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go
index f87d8ecf4..464de9793 100644
--- a/utils/issues/issuescollection.go
+++ b/utils/issues/issuescollection.go
@@ -28,6 +28,9 @@ type ScansIssuesCollection struct {
IacVulnerabilities []formats.SourceCodeRow
IacViolations []formats.SourceCodeRow
+ ServicesVulnerabilities []formats.SourceCodeRow
+ ServicesViolations []formats.SourceCodeRow
+
SecretsVulnerabilities []formats.SourceCodeRow
SecretsViolations []formats.SourceCodeRow
@@ -83,6 +86,13 @@ func (ic *ScansIssuesCollection) Append(issues *ScansIssuesCollection) {
if len(issues.IacViolations) > 0 {
ic.IacViolations = append(ic.IacViolations, issues.IacViolations...)
}
+ // 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...)
+ }
// Last-write-wins, not a true merge: today only one non-nil Violations source is ever appended
// (scan-pr calls Append exactly once, from the source-branch scan). Revisit if that changes.
if issues.Violations != nil {
@@ -97,6 +107,9 @@ func (ic *ScansIssuesCollection) AppendStatus(scanStatus formats.ScanStatus) {
if ic.IacStatusCode == nil || (*ic.IacStatusCode == 0 && scanStatus.IacStatusCode != nil) {
ic.IacStatusCode = scanStatus.IacStatusCode
}
+ if ic.ServicesStatusCode == nil || (*ic.ServicesStatusCode == 0 && scanStatus.ServicesStatusCode != nil) {
+ ic.ServicesStatusCode = scanStatus.ServicesStatusCode
+ }
if ic.SecretsStatusCode == nil || (*ic.SecretsStatusCode == 0 && scanStatus.SecretsStatusCode != nil) {
ic.SecretsStatusCode = scanStatus.SecretsStatusCode
}
@@ -120,6 +133,8 @@ func (ic *ScansIssuesCollection) GetScanStatus(scanType utils.SubScanType) *int
return ic.ScaStatusCode
case utils.IacScan:
return ic.IacStatusCode
+ case utils.ServicesScan:
+ return ic.ServicesStatusCode
case utils.SecretsScan:
return ic.SecretsStatusCode
case utils.SastScan:
@@ -141,6 +156,9 @@ func (ic *ScansIssuesCollection) HasErrors() bool {
if iacStatus := ic.GetScanStatus(utils.IacScan); iacStatus != nil && *iacStatus != 0 {
return true
}
+ if servicesStatus := ic.GetScanStatus(utils.ServicesScan); servicesStatus != nil && *servicesStatus != 0 {
+ return true
+ }
if secretsStatus := ic.GetScanStatus(utils.SecretsScan); secretsStatus != nil && *secretsStatus != 0 {
return true
}
@@ -180,6 +198,14 @@ func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubSc
if vulnerabilities {
jasVulnerabilities = ic.IacVulnerabilities
}
+ case utils.ServicesScan:
+ // Count Services issues only if requested
+ if isViolation {
+ jasViolations = ic.ServicesViolations
+ }
+ if vulnerabilities {
+ jasVulnerabilities = ic.ServicesVulnerabilities
+ }
case utils.SecretsScan:
// Count Secrets issues only if requested
if isViolation {
@@ -208,7 +234,7 @@ 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 {
@@ -219,6 +245,10 @@ func (ic *ScansIssuesCollection) IacIssuesExists() bool {
return len(ic.IacVulnerabilities) > 0 || len(ic.IacViolations) > 0
}
+func (ic *ScansIssuesCollection) ServicesIssuesExists() bool {
+ return len(ic.ServicesVulnerabilities) > 0 || len(ic.ServicesViolations) > 0
+}
+
func (ic *ScansIssuesCollection) SecretsIssuesExists() bool {
return len(ic.SecretsVulnerabilities) > 0 || len(ic.SecretsViolations) > 0
}
@@ -265,6 +295,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
}
@@ -344,7 +380,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)
}
@@ -358,7 +394,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 75c268e37..1cb208aa6 100644
--- a/utils/issues/issuescollection_test.go
+++ b/utils/issues/issuescollection_test.go
@@ -128,8 +128,9 @@ func getTestData() ScansIssuesCollection {
},
}},
- IacVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "Low"}}},
- SecretsVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "High"}}},
+ IacVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "Low"}}},
+ ServicesVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "Medium"}}},
+ SecretsVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "High"}}},
SecretsViolations: []formats.SourceCodeRow{{
SeverityDetails: formats.SeverityDetails{Severity: "High"},
ViolationContext: formats.ViolationContext{
@@ -158,12 +159,12 @@ func TestGetAllIssuesCount(t *testing.T) {
{
name: "With Secrets",
includeSecrets: true,
- expectedFindings: 9,
+ expectedFindings: 10,
},
{
name: "No Secrets",
includeSecrets: false,
- expectedFindings: 7,
+ expectedFindings: 8,
},
}
issuesCollection := getTestData()
@@ -184,12 +185,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()
@@ -273,6 +274,25 @@ func TestGetScanIssuesSeverityCount(t *testing.T) {
violation: true,
expectedSeverityCount: map[string]int{"Low": 1},
},
+ {
+ 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{},
+ },
+ {
+ name: "Services Vulnerabilities and Violations",
+ scanType: utils.ServicesScan,
+ vulnerabilities: true,
+ violation: true,
+ expectedSeverityCount: map[string]int{"Medium": 1},
+ },
{
name: "Secrets Vulnerabilities",
scanType: utils.SecretsScan,
@@ -378,6 +398,13 @@ func TestIssuesExists(t *testing.T) {
includeSecrets: true,
expected: true,
},
+ {
+ name: "With Services only",
+ issues: ScansIssuesCollection{
+ ServicesVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "High"}}},
+ },
+ expected: true,
+ },
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@@ -434,6 +461,12 @@ func TestIsFailPrRuleApplied(t *testing.T) {
scannerToAddFailPrRule: "iac",
expected: true,
},
+ {
+ name: "Services violations with fail PR rule",
+ issues: getTestData(),
+ scannerToAddFailPrRule: "services",
+ expected: true,
+ },
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@@ -466,6 +499,13 @@ func TestIsFailPrRuleApplied(t *testing.T) {
FailPr: true,
},
})
+ case "services":
+ tc.issues.ServicesViolations = append(tc.issues.ServicesViolations, formats.SourceCodeRow{
+ ViolationContext: formats.ViolationContext{
+ Watch: "services-watch",
+ FailPr: true,
+ },
+ })
}
assert.Equal(t, tc.expected, tc.issues.IsFailPrRuleApplied())
@@ -508,6 +548,18 @@ func TestHasErrors(t *testing.T) {
},
expected: true,
},
+ {
+ name: "With Services Errors",
+ status: formats.ScanStatus{
+ ScaStatusCode: utils.NewIntPtr(0),
+ SastStatusCode: utils.NewIntPtr(0),
+ SecretsStatusCode: utils.NewIntPtr(0),
+ IacStatusCode: utils.NewIntPtr(0),
+ ServicesStatusCode: utils.NewIntPtr(1),
+ ApplicabilityStatusCode: utils.NewIntPtr(0),
+ },
+ expected: true,
+ },
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@@ -519,9 +571,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(0),
}}
testCases := []struct {
name string
@@ -532,6 +585,10 @@ func TestIsScanNotCompleted(t *testing.T) {
name: "Scanned and Passed",
scan: utils.SastScan,
},
+ {
+ name: "Services Scanned and Passed",
+ scan: utils.ServicesScan,
+ },
{
name: "Scanned and unknown Failed",
scan: utils.ScaScan,
@@ -565,12 +622,14 @@ func TestAppendStatus(t *testing.T) {
SastStatusCode: utils.NewIntPtr(33),
ApplicabilityStatusCode: utils.NewIntPtr(0),
SecretsStatusCode: utils.NewIntPtr(51),
+ ServicesStatusCode: utils.NewIntPtr(7),
}
expectedStatus := formats.ScanStatus{
ScaStatusCode: utils.NewIntPtr(-1),
SastStatusCode: utils.NewIntPtr(33),
ApplicabilityStatusCode: utils.NewIntPtr(0),
SecretsStatusCode: utils.NewIntPtr(51),
+ ServicesStatusCode: utils.NewIntPtr(7),
}
issues := ScansIssuesCollection{ScanStatus: oldStatus}
issues.AppendStatus(newStatus)
diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go
index 7683378a2..f0b9c066f 100644
--- a/utils/outputwriter/outputcontent.go
+++ b/utils/outputwriter/outputcontent.go
@@ -35,6 +35,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)"
@@ -186,6 +187,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)), getScanSecurityIssuesDetails(issues, context, utils.ServicesScan, writer))
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()
@@ -232,6 +234,8 @@ func getScanSecurityIssuesDetails(issues issues.ScansIssuesCollection, context r
severityCountMap = issues.GetScanIssuesSeverityCount(utils.SecretsScan, countVulnerabilities, countViolations)
case utils.IacScan:
severityCountMap = issues.GetScanIssuesSeverityCount(utils.IacScan, countVulnerabilities, countViolations)
+ case utils.ServicesScan:
+ severityCountMap = issues.GetScanIssuesSeverityCount(utils.ServicesScan, countVulnerabilities, countViolations)
}
totalIssues := getTotalIssues(severityCountMap)
if totalIssues == 0 {
@@ -502,6 +506,22 @@ func IacReviewContent(violation bool, writer OutputWriter, issues ...formats.Sou
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(getJasIssueDescriptionTable(writer, issues...)),
+ getJasFullDescription(violation, writer, getServicesRuleFullDescriptionTable, issues...),
+ )
+ return contentBuilder.String()
+}
+
+func getServicesRuleFullDescriptionTable(info formats.ScannerInfo, writer OutputWriter) *MarkdownTableBuilder {
+ table := getBaseJasDetailsTable(info, writer)
+ table.AddRow(MarkAsBold("Rule ID:"), info.RuleId)
+ return table
+}
+
func SastReviewContent(violation bool, writer OutputWriter, issues ...formats.SourceCodeRow) string {
var contentBuilder strings.Builder
WriteContent(&contentBuilder,
diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go
index ca626aee5..3a854fc02 100644
--- a/utils/outputwriter/outputcontent_test.go
+++ b/utils/outputwriter/outputcontent_test.go
@@ -1247,6 +1247,84 @@ func TestIacReviewContent(t *testing.T) {
}
}
+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: "Medium"},
+ Finding: "Exposed service endpoint detected",
+ ScannerInfo: formats.ScannerInfo{
+ RuleId: "rule-id",
+ ScannerDescription: "Scanner Description....",
+ ScannerShortDescription: "Scanner Short Description",
+ },
+ }},
+ 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: "Medium"},
+ Finding: "Exposed service endpoint detected",
+ ScannerInfo: formats.ScannerInfo{
+ RuleId: "rule-id",
+ ScannerDescription: "Scanner Description....",
+ ScannerShortDescription: "Scanner Short Description",
+ },
+ ViolationContext: formats.ViolationContext{
+ IssueId: "services-violation-id",
+ Watch: "jas-watch",
+ Policies: []string{"policy1", "policy2"},
+ },
+ }},
+ 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 TestSastReviewContent(t *testing.T) {
testCases := []struct {
name string
diff --git a/utils/utils.go b/utils/utils.go
index 5f9367584..f0e7231fa 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -365,10 +365,12 @@ func GetVulnerabiltiesUniqueID(vulnerability formats.VulnerabilityOrViolationRow
func ConvertSarifPathsToRelative(issues *issues.ScansIssuesCollection, workingDirs ...string) {
convertSarifPathsInCveApplicability(issues.ScaVulnerabilities, workingDirs...)
convertSarifPathsInIacs(issues.IacVulnerabilities, workingDirs...)
+ convertSarifPathsInServices(issues.ServicesVulnerabilities, workingDirs...)
convertSarifPathsInSecrets(issues.SecretsVulnerabilities, workingDirs...)
convertSarifPathsInSast(issues.SastVulnerabilities, workingDirs...)
convertSarifPathsInCveApplicability(issues.ScaViolations, workingDirs...)
convertSarifPathsInIacs(issues.IacViolations, workingDirs...)
+ convertSarifPathsInServices(issues.ServicesViolations, workingDirs...)
convertSarifPathsInSecrets(issues.SecretsViolations, workingDirs...)
convertSarifPathsInSast(issues.SastViolations, workingDirs...)
}
@@ -396,6 +398,15 @@ func convertSarifPathsInIacs(iacs []formats.SourceCodeRow, workingDirs ...string
}
}
+func convertSarifPathsInServices(services []formats.SourceCodeRow, workingDirs ...string) {
+ for i := range services {
+ service := &services[i]
+ for _, wd := range workingDirs {
+ service.Location.File = utils.GetRelativePath(service.Location.File, wd)
+ }
+ }
+}
+
func convertSarifPathsInSecrets(secrets []formats.SourceCodeRow, workingDirs ...string) {
for i := range secrets {
secret := &secrets[i]
diff --git a/utils/utils_test.go b/utils/utils_test.go
index 6542b714b..586789ff2 100644
--- a/utils/utils_test.go
+++ b/utils/utils_test.go
@@ -23,6 +23,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/jfrog/frogbot/v3/utils/gitlabreport"
+ "github.com/jfrog/frogbot/v3/utils/issues"
"github.com/jfrog/frogbot/v3/utils/outputwriter"
)
@@ -34,6 +35,22 @@ const (
dependencySubmissionTestSha = "abc123def456"
)
+func TestConvertSarifPathsToRelativeServices(t *testing.T) {
+ issuesCollection := &issues.ScansIssuesCollection{
+ ServicesVulnerabilities: []formats.SourceCodeRow{{
+ Location: formats.Location{File: filepath.Join("tmp", "wd", "service.yaml")},
+ }},
+ ServicesViolations: []formats.SourceCodeRow{{
+ Location: formats.Location{File: filepath.Join("tmp", "wd", "api.yaml")},
+ }},
+ }
+
+ ConvertSarifPathsToRelative(issuesCollection, filepath.Join("tmp", "wd"))
+
+ assert.Equal(t, "service.yaml", issuesCollection.ServicesVulnerabilities[0].Location.File)
+ assert.Equal(t, "api.yaml", issuesCollection.ServicesViolations[0].Location.File)
+}
+
func TestChdir(t *testing.T) {
originCwd, err := os.Getwd()
assert.NoError(t, err)