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
2 Issues Found
2 High
High | CVE-2022-29217 | Not Covered | pyjwt:1.7.1 | pyjwt 1.7.1 | [2.4.0] |
+| 
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).2 Issues Found
2 High
3 Issues Found
3 High
High | CVE-2022-3517 | Not Applicable | minimatch:3.0.4 | minimatch 3.0.4 | [3.0.5] |
| 
High | CVE-2022-29217 | Not Covered | pyjwt:1.7.1 | pyjwt 1.7.1 | [2.4.0] |
+| 
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).