diff --git a/integrationutils.go b/integrationutils.go index d30230e36..36d15fdbd 100644 --- a/integrationutils.go +++ b/integrationutils.go @@ -3,6 +3,12 @@ package main import ( "context" "fmt" + "os" + "strconv" + "strings" + "testing" + "time" + "github.com/go-git/go-git/v5" githttp "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/jfrog/frogbot/v2/scanpullrequest" @@ -13,11 +19,6 @@ import ( "github.com/jfrog/froggit-go/vcsutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "os" - "strconv" - "strings" - "testing" - "time" ) const ( @@ -76,18 +77,19 @@ func setIntegrationTestEnvs(t *testing.T, testDetails *IntegrationTestDetails) f // so we restore them at the end of the test to avoid collisions with other tests envRestoreFunc := getJfrogEnvRestoreFunc(t) unsetEnvs := utils.SetEnvsAndAssertWithCallback(t, map[string]string{ - utils.RequirementsFileEnv: "requirements.txt", - utils.GitPullRequestIDEnv: testDetails.PullRequestID, - utils.GitProvider: testDetails.GitProvider, - utils.GitTokenEnv: testDetails.GitToken, - utils.GitRepoEnv: testDetails.RepoName, - utils.GitRepoOwnerEnv: testDetails.RepoOwner, - utils.BranchNameTemplateEnv: testDetails.CustomBranchName, - utils.GitApiEndpointEnv: testDetails.ApiEndpoint, - utils.GitProjectEnv: testDetails.GitProject, - utils.GitUsernameEnv: testDetails.GitUsername, - utils.GitBaseBranchEnv: mainBranch, - utils.GitUseLocalRepositoryEnv: fmt.Sprintf("%t", testDetails.UseLocalRepo), + utils.RequirementsFileEnv: "requirements.txt", + utils.GitPullRequestIDEnv: testDetails.PullRequestID, + utils.GitProvider: testDetails.GitProvider, + utils.GitTokenEnv: testDetails.GitToken, + utils.GitRepoEnv: testDetails.RepoName, + utils.GitRepoOwnerEnv: testDetails.RepoOwner, + utils.BranchNameTemplateEnv: testDetails.CustomBranchName, + utils.GitApiEndpointEnv: testDetails.ApiEndpoint, + utils.GitProjectEnv: testDetails.GitProject, + utils.GitUsernameEnv: testDetails.GitUsername, + utils.GitBaseBranchEnv: mainBranch, + utils.GitUseLocalRepositoryEnv: fmt.Sprintf("%t", testDetails.UseLocalRepo), + utils.IncludeVulnerabilitiesEnv: "TRUE", }) return func() { envRestoreFunc() @@ -232,6 +234,15 @@ func validateResults(t *testing.T, ctx context.Context, client vcsclient.VcsClie comments, err := client.ListPullRequestComments(ctx, testDetails.RepoOwner, testDetails.RepoName, prID) require.NoError(t, err) + t.Logf("ListPullRequestComments returned %d comments", len(comments)) + for i, comment := range comments { + contentPreview := comment.Content + if len(contentPreview) > 100 { + contentPreview = contentPreview[:100] + "..." + } + t.Logf("Comment %d: ID=%d, Content preview: %s", i, comment.ID, contentPreview) + } + switch actualClient := client.(type) { case *vcsclient.GitHubClient: validateGitHubComments(t, ctx, actualClient, testDetails, prID, comments) @@ -240,7 +251,7 @@ func validateResults(t *testing.T, ctx context.Context, client vcsclient.VcsClie case *vcsclient.BitbucketServerClient: validateBitbucketServerComments(t, comments) case *vcsclient.GitLabClient: - validateGitLabComments(t, comments) + validateGitLabComments(t, ctx, actualClient, testDetails, prID, comments) } } @@ -264,9 +275,18 @@ func validateBitbucketServerComments(t *testing.T, comments []vcsclient.CommentI assertBannerExists(t, comments, outputwriter.GetSimplifiedTitle(outputwriter.VulnerabilitiesPrBannerSource)) } -func validateGitLabComments(t *testing.T, comments []vcsclient.CommentInfo) { - assert.GreaterOrEqual(t, len(comments), expectedNumberOfIssues) +func validateGitLabComments(t *testing.T, ctx context.Context, client *vcsclient.GitLabClient, testDetails *IntegrationTestDetails, prID int, comments []vcsclient.CommentInfo) { + // GitLab separates regular comments (notes) from review comments (discussions) + // The summary comment with banner should be in regular comments + t.Logf("Regular comments (notes): %d", len(comments)) assertBannerExists(t, comments, string(outputwriter.VulnerabilitiesMrBannerSource)) + + // Review comments are stored as discussions + reviewComments, err := client.ListPullRequestReviewComments(ctx, testDetails.RepoOwner, testDetails.RepoName, prID) + assert.NoError(t, err) + t.Logf("Review comments (discussions): %d", len(reviewComments)) + // We expect at least the review comments (IAC, SAST, Applicable) + assert.GreaterOrEqual(t, len(reviewComments), 1) } func getJfrogEnvRestoreFunc(t *testing.T) func() { diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index 68feb6901..10b11c6e0 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -98,7 +98,7 @@ func TestScanResultsToIssuesCollection(t *testing.T) { Applicable: "Applicable", FixedVersions: []string{"1.2.3"}, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - SeverityDetails: formats.SeverityDetails{Severity: "High", SeverityNumValue: 21}, + SeverityDetails: formats.SeverityDetails{Severity: "High", SeverityNumValue: 26}, ImpactedDependencyName: "Dep-1", }, Cves: []formats.CveRow{{Id: "CVE-2022-2122", Applicability: &formats.Applicability{Status: "Applicable", ScannerDescription: "rule-msg", Evidence: []formats.Evidence{{Reason: "result-msg", Location: formats.Location{File: "file1", StartLine: 1, StartColumn: 10, EndLine: 2, EndColumn: 11, Snippet: "snippet"}}}}}}, @@ -107,7 +107,7 @@ func TestScanResultsToIssuesCollection(t *testing.T) { Applicable: "Not Applicable", FixedVersions: []string{"1.2.2"}, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - SeverityDetails: formats.SeverityDetails{Severity: "Low", SeverityNumValue: 2}, + SeverityDetails: formats.SeverityDetails{Severity: "Low", SeverityNumValue: 3}, ImpactedDependencyName: "Dep-2", }, Cves: []formats.CveRow{{Id: "CVE-2023-3122", Applicability: &formats.Applicability{Status: "Not Applicable", ScannerDescription: "rule-msg"}}}, @@ -117,7 +117,7 @@ func TestScanResultsToIssuesCollection(t *testing.T) { { SeverityDetails: formats.SeverityDetails{ Severity: "High", - SeverityNumValue: 21, + SeverityNumValue: 26, }, ScannerInfo: formats.ScannerInfo{ ScannerDescription: "rule-msg", @@ -138,7 +138,7 @@ func TestScanResultsToIssuesCollection(t *testing.T) { { SeverityDetails: formats.SeverityDetails{ Severity: "High", - SeverityNumValue: 21, + SeverityNumValue: 26, }, ScannerInfo: formats.ScannerInfo{ ScannerDescription: "rule-msg", @@ -159,7 +159,7 @@ func TestScanResultsToIssuesCollection(t *testing.T) { { SeverityDetails: formats.SeverityDetails{ Severity: "High", - SeverityNumValue: 21, + SeverityNumValue: 26, }, ScannerInfo: formats.ScannerInfo{ ScannerDescription: "rule-msg", @@ -183,7 +183,7 @@ func TestScanResultsToIssuesCollection(t *testing.T) { ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ SeverityDetails: formats.SeverityDetails{ Severity: "Medium", - SeverityNumValue: 14, + SeverityNumValue: 19, }, ImpactedDependencyName: "Dep-1", }, diff --git a/testdata/messages/integration/test_proj_pip_with_vulnerability.md b/testdata/messages/integration/test_proj_pip_with_vulnerability.md index 00779aec8..b16086630 100644 --- a/testdata/messages/integration/test_proj_pip_with_vulnerability.md +++ b/testdata/messages/integration/test_proj_pip_with_vulnerability.md @@ -11,11 +11,11 @@ ## 📗 Scan Summary -- Frogbot scanned for vulnerabilities and found 1 issues +- Frogbot scanned for vulnerabilities and found 2 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | -| **Software Composition Analysis** | ✅ Done |
1 Issues Found 1 High
| +| **Software Composition Analysis** | ✅ Done |
2 Issues Found 2 High
| | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | Not Found | | **Secrets** | ✅ Done | - | @@ -28,6 +28,7 @@ | Severity | ID | Contextual Analysis | Direct Dependencies | Impacted Dependency | Fixed Versions | | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | | ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | CVE-2022-29217 | Not Covered | pyjwt:1.7.1 | pyjwt 1.7.1 | [2.4.0] | +| ![high (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableHigh.png)
High | CVE-2025-45768 | Not Applicable | pyjwt:1.7.1 | pyjwt 1.7.1 | - | @@ -35,6 +36,7 @@ ### 🔖 Details +
[ CVE-2022-29217 ] pyjwt 1.7.1 ### Vulnerability Details | | | @@ -67,17 +69,7 @@ For example, an application might have planned to validate an `EdDSA`-signed tok # Making a good jwt token that should work by signing it with the private key encoded_good = jwt.encode({"test": 1234}, priv_key_bytes, algorithm="EdDSA") ``` -An attacker in posession of the public key can generate an `HMAC`-signed token to confuse PyJWT - -```python -# Using HMAC with the public key to trick the receiver to think that the public key is a HMAC secret -encoded_bad = jwt.encode({"test": 1234}, pub_key_bytes, algorithm="HS256") -``` - -The following vulnerable `decode` call will accept BOTH of the above tokens as valid - -``` -decoded = jwt.decode(encoded_good, pub_key_bytes, -algorithms=jwt.algorithms.get_default_algorithms()) -``` +A... **Remediation:** ##### Development mitigations @@ -87,7 +79,20 @@ For example, replace the following call - `jwt.decode(encoded_jwt, pub_key_bytes, algorithms=jwt.algorithms.get_default_algorithms())` With - `jwt.decode(encoded_jwt, pub_key_bytes, algorithms=["ES256"])` +
+ +
[ CVE-2025-45768 ] pyjwt 1.7.1 + +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Not Applicable | +| **Direct Dependencies:** | pyjwt:1.7.1 | +| **Impacted Dependency:** | pyjwt:1.7.1 | +| **Fixed Versions:** | - | +| **CVSS V3:** | 7.0 | +pyjwt v2.10.1 was discovered to contain weak encryption. NOTE: this is disputed by the Supplier because the key length is chosen by the application that uses the library (admittedly, library users may benefit from a minimum value and a mechanism for opting in to strict enforcement).
--- diff --git a/testdata/scanpullrequest/expected_response_multi_dir.md b/testdata/scanpullrequest/expected_response_multi_dir.md index e83ab7d5d..197a8aafb 100644 --- a/testdata/scanpullrequest/expected_response_multi_dir.md +++ b/testdata/scanpullrequest/expected_response_multi_dir.md @@ -11,11 +11,11 @@ ## 📗 Scan Summary -- Frogbot scanned for vulnerabilities and found 2 issues +- Frogbot scanned for vulnerabilities and found 3 issues | Scan Category | Status | Security Issues | | --------------------- | :-----------------------------------: | ----------------------------------- | -| **Software Composition Analysis** | ✅ Done |
2 Issues Found 2 High
| +| **Software Composition Analysis** | ✅ Done |
3 Issues Found 3 High
| | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | Not Found | | **Secrets** | ✅ Done | - | @@ -29,6 +29,7 @@ | :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | | ![high (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableHigh.png)
High | CVE-2022-3517 | Not Applicable | minimatch:3.0.4 | minimatch 3.0.4 | [3.0.5] | | ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | CVE-2022-29217 | Not Covered | pyjwt:1.7.1 | pyjwt 1.7.1 | [2.4.0] | +| ![high (not applicable)](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableHigh.png)
High | CVE-2025-45768 | Not Applicable | pyjwt:1.7.1 | pyjwt 1.7.1 | - | @@ -82,17 +83,7 @@ For example, an application might have planned to validate an `EdDSA`-signed tok # Making a good jwt token that should work by signing it with the private key encoded_good = jwt.encode({"test": 1234}, priv_key_bytes, algorithm="EdDSA") ``` -An attacker in posession of the public key can generate an `HMAC`-signed token to confuse PyJWT - -```python -# Using HMAC with the public key to trick the receiver to think that the public key is a HMAC secret -encoded_bad = jwt.encode({"test": 1234}, pub_key_bytes, algorithm="HS256") -``` - -The following vulnerable `decode` call will accept BOTH of the above tokens as valid - -``` -decoded = jwt.decode(encoded_good, pub_key_bytes, -algorithms=jwt.algorithms.get_default_algorithms()) -``` +A... **Remediation:** ##### Development mitigations @@ -104,6 +95,19 @@ With - `jwt.decode(encoded_jwt, pub_key_bytes, algorithms=["ES256"])`
+
[ CVE-2025-45768 ] pyjwt 1.7.1 + +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Contextual Analysis:** | Not Applicable | +| **Direct Dependencies:** | pyjwt:1.7.1 | +| **Impacted Dependency:** | pyjwt:1.7.1 | +| **Fixed Versions:** | - | +| **CVSS V3:** | 7.0 | + +pyjwt v2.10.1 was discovered to contain weak encryption. NOTE: this is disputed by the Supplier because the key length is chosen by the application that uses the library (admittedly, library users may benefit from a minimum value and a mechanism for opting in to strict enforcement).
+ ---
diff --git a/utils/comment.go b/utils/comment.go index 6c1563a11..e98853bb4 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -48,8 +48,8 @@ func HandlePullRequestCommentsAfterScan(issues *issues.ScansIssuesCollection, re } // Add summary (SCA, license) scan comment - if issues.IssuesExists(repo.PullRequestSecretComments) || repo.AddPrCommentOnSuccess { - for _, comment := range generatePullRequestSummaryComment(*issues, resultContext, repo.PullRequestSecretComments, repo.OutputWriter) { + if issues.IssuesExists(repo.Params.PullRequestSecretComments) || repo.AddPrCommentOnSuccess { + for _, comment := range generatePullRequestSummaryComment(*issues, resultContext, repo.Params.PullRequestSecretComments, repo.OutputWriter) { if err = client.AddPullRequestComment(context.Background(), repo.RepoOwner, repo.RepoName, comment, pullRequestID); err != nil { err = errors.New("couldn't add pull request comment: " + err.Error()) return