From 6a3e3c5aeaff787e32d36678d8f5f63227ef5f90 Mon Sep 17 00:00:00 2001 From: Eyal Delarea Date: Wed, 28 Jun 2023 16:34:14 +0300 Subject: [PATCH 1/3] Restrict aggregate mode to a single active branch (#367) --- commands/createfixpullrequests.go | 170 +++++++++++++++++++++--------- commands/utils/consts.go | 2 +- commands/utils/git.go | 38 +++++-- commands/utils/git_test.go | 51 ++------- commands/utils/params.go | 15 ++- commands/utils/params_test.go | 2 +- commands/utils/utils.go | 2 +- commands/utils/utils_test.go | 2 +- go.mod | 6 +- go.sum | 9 +- 10 files changed, 183 insertions(+), 114 deletions(-) diff --git a/commands/createfixpullrequests.go b/commands/createfixpullrequests.go index 88f611dfe..f83c9e34f 100644 --- a/commands/createfixpullrequests.go +++ b/commands/createfixpullrequests.go @@ -7,6 +7,7 @@ import ( "github.com/jfrog/frogbot/commands/utils" "github.com/jfrog/frogbot/commands/utils/packagehandlers" "github.com/jfrog/froggit-go/vcsclient" + "github.com/jfrog/froggit-go/vcsutils" "github.com/jfrog/gofrog/version" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" audit "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/generic" @@ -19,6 +20,10 @@ import ( "strings" ) +const ( + PullRequestNotFound = -1 +) + type CreateFixPullRequestsCmd struct { // The interface that Frogbot utilizes to format and style the displayed messages on the Git providers utils.OutputWriter @@ -150,7 +155,7 @@ func (cfp *CreateFixPullRequestsCmd) fixIssuesSeparatePRs(vulnerabilitiesMap map } // After finishing to work on the current vulnerability, we go back to the base branch to start the next vulnerability fix log.Debug("Running git checkout to base branch:", cfp.details.Branch()) - if err = cfp.gitManager.Checkout(cfp.details.Branch()); err != nil { + if err = cfp.gitManager.CheckoutLocalBranch(cfp.details.Branch()); err != nil { return } } @@ -159,39 +164,27 @@ func (cfp *CreateFixPullRequestsCmd) fixIssuesSeparatePRs(vulnerabilitiesMap map return } -func (cfp *CreateFixPullRequestsCmd) fixIssuesSinglePR(vulnerabilities map[string]*utils.VulnerabilityDetails) (err error) { - var errList strings.Builder - var atLeastOneFix bool - log.Info("-----------------------------------------------------------------") - log.Info("Starting aggregated dependencies fix") - aggregatedFixBranchName, err := cfp.gitManager.GenerateAggregatedFixBranchName(vulnerabilities) +// fixIssuesSinglePR fixes all the vulnerabilities in a single aggregated pull request. +// If an existing aggregated fix is present, it checks for different scan results. +// If the scan results are the same, no action is taken. +// Otherwise, it performs a force push to the same branch and reopens the pull request if it was closed. +// Only one aggregated pull request should remain open at all times. +func (cfp *CreateFixPullRequestsCmd) fixIssuesSinglePR(vulnerabilityDetails map[string]*utils.VulnerabilityDetails) (err error) { + aggregatedFixBranchName, err := cfp.gitManager.GenerateAggregatedFixBranchName() if err != nil { return } - log.Debug("Creating branch", aggregatedFixBranchName, "...") - if err = cfp.gitManager.CreateBranchAndCheckout(aggregatedFixBranchName); err != nil { + existingPullRequestId, err := cfp.getOpenPullRequestIdBySourceBranch(aggregatedFixBranchName) + if err != nil { return } - // Fix all packages in the same branch if expected error accrued, log and continue. - var fixedVulnerabilities []formats.VulnerabilityOrViolationRow - for _, vulnDetails := range vulnerabilities { - if err = cfp.updatePackageToFixedVersion(vulnDetails); err != nil { - cfp.handleUpdatePackageErrors(err, errList) - } else { - vulnDetails.FixedVersions = []string{vulnDetails.FixVersion} - fixedVulnerabilities = append(fixedVulnerabilities, *vulnDetails.VulnerabilityOrViolationRow) - log.Info(fmt.Sprintf("Updated dependency '%s' to version '%s'", vulnDetails.ImpactedDependencyName, vulnDetails.FixVersion)) - atLeastOneFix = true - } - } - if atLeastOneFix { - if err = cfp.openAggregatedPullRequest(aggregatedFixBranchName, fixedVulnerabilities); err != nil { - return fmt.Errorf("failed while creating aggreagted pull request. Error: \n%s", err.Error()) + if existingPullRequestId != PullRequestNotFound { + if identicalScanResults, err := cfp.compareScanResults(vulnerabilityDetails, aggregatedFixBranchName); identicalScanResults || err != nil { + log.Info("The scan results have not changed since the last Frogbot run.") + return err } } - logAppendedErrorsIfExists(errList) - log.Info("-----------------------------------------------------------------") - return + return cfp.aggregateFixAndOpenPullRequest(vulnerabilityDetails, aggregatedFixBranchName, existingPullRequestId) } // Handles possible error of update package operation @@ -265,38 +258,34 @@ func (cfp *CreateFixPullRequestsCmd) openFixingPullRequest(fixBranchName string, return cfp.details.Client().CreatePullRequest(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName, fixBranchName, cfp.details.Branch(), pullRequestTitle, prBody) } -// When aggregate mode is active, there can be only one updated pull request to contain all the available fixes. -// In case of an already opened pull request, Frogbot will only update the branch. -func (cfp *CreateFixPullRequestsCmd) openAggregatedPullRequest(fixBranchName string, vulnerabilities []formats.VulnerabilityOrViolationRow) (err error) { +// openAggregatedPullRequest handles the opening or updating of a pull request when the aggregate mode is active. +// If a pull request is already open, Frogbot will update the branch and the pull request body. +func (cfp *CreateFixPullRequestsCmd) openAggregatedPullRequest(fixBranchName string, existingPullRequestId int64, vulnerabilities []formats.VulnerabilityOrViolationRow) (err error) { log.Debug("Checking if there are changes to commit") isClean, err := cfp.gitManager.IsClean() if err != nil { return } - if isClean { - log.Debug("Couldn't fix any of the impacted dependencies") - return - } - commitMessage := cfp.gitManager.GenerateAggregatedCommitMessage() - log.Debug("Running git add all and commit...") - if err = cfp.gitManager.AddAllAndCommit(commitMessage); err != nil { - return - } - exists, err := cfp.gitManager.BranchExistsInRemote(fixBranchName) - if err != nil { - return - } - log.Debug("Pushing branch:", fixBranchName, "...") - if err = cfp.gitManager.Push(true, fixBranchName); err != nil { - return + if !isClean { + commitMessage := cfp.gitManager.GenerateAggregatedCommitMessage() + log.Debug("Running git add all and commit...") + if err = cfp.gitManager.AddAllAndCommit(commitMessage); err != nil { + return + } + log.Debug("Pushing branch:", fixBranchName, "...") + if err = cfp.gitManager.Push(true, fixBranchName); err != nil { + return + } } - if !exists { + // Even if the git state is clean, there are cases where we still need to update the pull request body. + prBody := cfp.OutputWriter.VulnerabiltiesTitle(false) + "\n" + cfp.OutputWriter.VulnerabilitiesContent(vulnerabilities) + pullRequestTitle := utils.AggregatedPullRequestTitleTemplate + if existingPullRequestId == PullRequestNotFound { log.Info("Creating Pull Request from:", fixBranchName, "to:", cfp.details.Branch()) - prBody := cfp.OutputWriter.VulnerabiltiesTitle(false) + "\n" + cfp.OutputWriter.VulnerabilitiesContent(vulnerabilities) - return cfp.details.Client().CreatePullRequest(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName, fixBranchName, cfp.details.Branch(), utils.AggregatedPullRequestTitleTemplate, prBody) + return cfp.details.Client().CreatePullRequest(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName, fixBranchName, cfp.details.Branch(), pullRequestTitle, prBody) } - log.Info("Pull Request branch:", fixBranchName, "has been updated") - return + log.Info("Updating Pull Request from:", fixBranchName, "to:", cfp.details.Branch()) + return cfp.details.Client().UpdatePullRequest(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName, pullRequestTitle, prBody, "", int(existingPullRequestId), vcsutils.Open) } func (cfp *CreateFixPullRequestsCmd) cloneRepository() (tempWd string, restoreDir func() error, err error) { @@ -388,6 +377,85 @@ func (cfp *CreateFixPullRequestsCmd) updatePackageToFixedVersion(vulnDetails *ut return cfp.handlers[vulnDetails.Technology].UpdateDependency(vulnDetails) } +// Computes the MD5 hash of a FixVersionMap object originated from the remote branch scan results +func (cfp *CreateFixPullRequestsCmd) getRemoteBranchScanHash(remoteBranchName string) (hash string, err error) { + if err = cfp.gitManager.CheckoutRemoteBranch(remoteBranchName); err != nil { + return + } + wd, err := os.Getwd() + if err != nil { + return + } + res, err := cfp.scan(wd) + if err != nil { + return + } + targetFixVersionMap, err := cfp.createVulnerabilitiesMap(res.ExtendedScanResults, res.IsMultipleRootProject) + if err != nil { + return + } + return utils.FixVersionsMapToMd5Hash(targetFixVersionMap) +} + +// Retrieves the ID of an open pull request by source branch name. +// Returns -1 if there is no open pull request. +func (cfp *CreateFixPullRequestsCmd) getOpenPullRequestIdBySourceBranch(branchName string) (pullRequestId int64, err error) { + list, err := cfp.details.Client().ListOpenPullRequests(context.Background(), cfp.details.RepoOwner, cfp.details.RepoName) + if err != nil { + return + } + for _, pr := range list { + if pr.Source.Name == branchName { + return pr.ID, nil + } + } + return PullRequestNotFound, nil +} + +func (cfp *CreateFixPullRequestsCmd) aggregateFixAndOpenPullRequest(vulnerabilities map[string]*utils.VulnerabilityDetails, aggregatedFixBranchName string, existingPullRequestId int64) (err error) { + var errList strings.Builder + var atLeastOneFix bool + log.Info("-----------------------------------------------------------------") + log.Info("Starting aggregated dependencies fix") + log.Debug("Creating branch", aggregatedFixBranchName, "...") + if err = cfp.gitManager.CreateBranchAndCheckout(aggregatedFixBranchName); err != nil { + return + } + // Fix all packages in the same branch if expected error accrued, log and continue. + var fixedVulnerabilities []formats.VulnerabilityOrViolationRow + for _, vulnDetails := range vulnerabilities { + if err = cfp.updatePackageToFixedVersion(vulnDetails); err != nil { + cfp.handleUpdatePackageErrors(err, errList) + } else { + vulnDetails.FixedVersions = []string{vulnDetails.FixVersion} + fixedVulnerabilities = append(fixedVulnerabilities, *vulnDetails.VulnerabilityOrViolationRow) + log.Info(fmt.Sprintf("Updated dependency '%s' to version '%s'", vulnDetails.ImpactedDependencyName, vulnDetails.FixVersion)) + atLeastOneFix = true + } + } + if atLeastOneFix { + if err = cfp.openAggregatedPullRequest(aggregatedFixBranchName, existingPullRequestId, fixedVulnerabilities); err != nil { + return fmt.Errorf("failed while creating aggreagted pull request. Error: \n%s", err.Error()) + } + } + logAppendedErrorsIfExists(errList) + log.Info("-----------------------------------------------------------------") + return err +} + +// Compares the scan results of a remote branch by computing the MD5 hash of the created FixVersionMap. +func (cfp *CreateFixPullRequestsCmd) compareScanResults(fixVersionsMap map[string]*utils.VulnerabilityDetails, aggregatedFixBranchName string) (identical bool, err error) { + currentScanHash, err := utils.FixVersionsMapToMd5Hash(fixVersionsMap) + if err != nil { + return + } + remoteBranchScanHash, err := cfp.getRemoteBranchScanHash(aggregatedFixBranchName) + if err != nil { + return + } + return currentScanHash == remoteBranchScanHash, err +} + func isBuildToolsDependency(vulnDetails *utils.VulnerabilityDetails) error { // Skip build tools dependencies (for example, pip) // that are not defined in the descriptor file and cannot be fixed by a PR. diff --git a/commands/utils/consts.go b/commands/utils/consts.go index fa8cf587a..655ef5c5f 100644 --- a/commands/utils/consts.go +++ b/commands/utils/consts.go @@ -98,7 +98,7 @@ const ( // Default naming templates BranchNameTemplate = "frogbot-" + PackagePlaceHolder + "-" + BranchHashPlaceHolder - AggregatedBranchNameTemplate = "frogobt-" + BranchHashPlaceHolder + AggregatedBranchNameTemplate = "frogbot-update-dependencies-" + BranchHashPlaceHolder CommitMessageTemplate = "Upgrade " + PackagePlaceHolder + " to " + FixVersionPlaceHolder AggregatedPullRequestTitleTemplate = "[🐸 Frogbot] Update dependencies versions" PullRequestTitleTemplate = "[🐸 Frogbot] Update version of " + PackagePlaceHolder + " to " + FixVersionPlaceHolder diff --git a/commands/utils/git.go b/commands/utils/git.go index 70746923a..4259a3430 100644 --- a/commands/utils/git.go +++ b/commands/utils/git.go @@ -34,6 +34,10 @@ const ( gitLabHttpsFormat = "%s/%s/%s.git" bitbucketServerHttpsFormat = "%s/scm/%s/%s.git" azureDevopsHttpsFormat = "https://%s@%s%s/_git/%s" + + // Aggregate branches name should always be the same name. + // We use a const to replace in the branch template ${BRANCH_NAME_HASH} + constAggregatedHash = "0" ) type GitManager struct { @@ -76,7 +80,7 @@ func NewGitManager(dryRun bool, clonedRepoPath, projectPath, remoteName, token, return &GitManager{repository: repository, dryRunRepoPath: clonedRepoPath, remoteName: remoteName, auth: basicAuth, dryRun: dryRun, customTemplates: templates, git: g}, nil } -func (gm *GitManager) Checkout(branchName string) error { +func (gm *GitManager) CheckoutLocalBranch(branchName string) error { err := gm.createBranchAndCheckout(branchName, false) if err != nil { err = fmt.Errorf("'git checkout %s' failed with error: %s", branchName, err.Error()) @@ -320,17 +324,14 @@ func (gm *GitManager) GeneratePullRequestTitle(impactedPackage string, version s return formatStringWithPlaceHolders(template, impactedPackage, version, "", true) } -// Generates unique branch name constructed by all the vulnerable packages. -func (gm *GitManager) GenerateAggregatedFixBranchName(versionsMap map[string]*VulnerabilityDetails) (fixBranchName string, err error) { - hash, err := fixVersionsMapToMd5Hash(versionsMap) - if err != nil { - return - } +// GenerateAggregatedFixBranchName Generating a consistent branch name to enable branch updates +// and to ensure that there is only one Frogbot branch in aggregated mode. +func (gm *GitManager) GenerateAggregatedFixBranchName() (fixBranchName string, err error) { branchFormat := gm.customTemplates.branchNameTemplate if branchFormat == "" { branchFormat = AggregatedBranchNameTemplate } - return formatStringWithPlaceHolders(branchFormat, "", "", hash, false), nil + return formatStringWithPlaceHolders(branchFormat, "", "", constAggregatedHash, false), nil } // dryRunClone clones an existing repository from our testdata folder into the destination folder for testing purposes. @@ -373,6 +374,27 @@ func (gm *GitManager) generateHTTPSCloneUrl() (url string, err error) { } } +func (gm *GitManager) CheckoutRemoteBranch(branchName string) error { + var checkoutConfig *git.CheckoutOptions + if gm.dryRun { + // On dry runs we mimic remote as local branches. + checkoutConfig = &git.CheckoutOptions{ + Branch: plumbing.NewBranchReferenceName(branchName), + Force: true, + } + } else { + checkoutConfig = &git.CheckoutOptions{ + Branch: plumbing.NewRemoteReferenceName(gm.remoteName, branchName), + Force: true, + } + } + worktree, err := gm.repository.Worktree() + if err != nil { + return err + } + return worktree.Checkout(checkoutConfig) +} + func toBasicAuth(token, username string) *githttp.BasicAuth { // The username can be anything except for an empty string if username == "" { diff --git a/commands/utils/git_test.go b/commands/utils/git_test.go index c2186f55b..4441db8b3 100644 --- a/commands/utils/git_test.go +++ b/commands/utils/git_test.go @@ -3,8 +3,6 @@ package utils import ( "github.com/jfrog/froggit-go/vcsclient" "github.com/jfrog/froggit-go/vcsutils" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli-core/v2/xray/formats" "github.com/stretchr/testify/assert" "testing" ) @@ -122,55 +120,26 @@ func TestGitManager_GeneratePullRequestTitle(t *testing.T) { func TestGitManager_GenerateAggregatedFixBranchName(t *testing.T) { tests := []struct { - fixVersionMapFirst map[string]*VulnerabilityDetails - fixVersionMapSecond map[string]*VulnerabilityDetails - gitManager GitManager - equal bool - desc string + gitManager GitManager + expected string + desc string }{ { - fixVersionMapFirst: map[string]*VulnerabilityDetails{ - "pkg": {FixVersion: "1.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}, - "pkg2": {FixVersion: "1.5.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}}, - fixVersionMapSecond: map[string]*VulnerabilityDetails{ - "pkg": {FixVersion: "1.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}, - "pkg2": {FixVersion: "1.5.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}}, - equal: true, desc: "should be equal", + expected: "frogbot-update-dependencies-0", + desc: "No template", gitManager: GitManager{}, }, { - fixVersionMapFirst: map[string]*VulnerabilityDetails{ - "pkg": {FixVersion: "1.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}, - "pkg2": {FixVersion: "1.5.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}, - }, - fixVersionMapSecond: map[string]*VulnerabilityDetails{ - "pkgOther": {FixVersion: "1.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}, - "pkg2": {FixVersion: "1.5.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}}, - equal: false, - desc: "should not be equal", - gitManager: GitManager{}, - }, - { - fixVersionMapFirst: map[string]*VulnerabilityDetails{ - "pkg": {FixVersion: "1.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}, - "pkg2": {FixVersion: "1.5.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}, - }, - fixVersionMapSecond: map[string]*VulnerabilityDetails{ - "pkgOther": {FixVersion: "1.2.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}, - "pkg2": {FixVersion: "1.5.3", VulnerabilityOrViolationRow: &formats.VulnerabilityOrViolationRow{Technology: coreutils.Npm}, IsDirectDependency: false}}, - equal: true, - desc: "should be equal with template", - gitManager: GitManager{customTemplates: CustomTemplates{branchNameTemplate: "custom"}}, + expected: "[feature]-0", + desc: "Custom template hash only", + gitManager: GitManager{customTemplates: CustomTemplates{branchNameTemplate: "[feature]-${BRANCH_NAME_HASH}"}}, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - titleOutput1, err := test.gitManager.GenerateAggregatedFixBranchName(test.fixVersionMapFirst) - assert.NoError(t, err) - titleOutput2, err := test.gitManager.GenerateAggregatedFixBranchName(test.fixVersionMapSecond) + titleOutput, err := test.gitManager.GenerateAggregatedFixBranchName() assert.NoError(t, err) - equal := titleOutput1 == titleOutput2 - assert.Equal(t, test.equal, equal) + assert.Equal(t, test.expected, titleOutput) }) } } diff --git a/commands/utils/params.go b/commands/utils/params.go index 215291498..c9a00762e 100644 --- a/commands/utils/params.go +++ b/commands/utils/params.go @@ -209,7 +209,11 @@ func (g *Git) setDefaultsIfNeeded(clientInfo *ClientInfo) (err error) { g.Branches = append(g.Branches, clientInfo.Branches...) } if g.BranchNameTemplate == "" { - g.BranchNameTemplate = getTrimmedEnv(BranchNameTemplateEnv) + branchTemplate := getTrimmedEnv(BranchNameTemplateEnv) + if err = validateHashPlaceHolder(branchTemplate); err != nil { + return + } + g.BranchNameTemplate = branchTemplate } if g.CommitMessageTemplate == "" { g.CommitMessageTemplate = getTrimmedEnv(CommitMessageTemplateEnv) @@ -229,6 +233,13 @@ func (g *Git) setDefaultsIfNeeded(clientInfo *ClientInfo) (err error) { return } +func validateHashPlaceHolder(template string) error { + if template != "" && !strings.Contains(template, BranchHashPlaceHolder) { + return fmt.Errorf("branch name template must contain %s", BranchHashPlaceHolder) + } + return nil +} + func GetFrogbotUtils() (frogbotUtils *FrogbotUtils, err error) { // Get server and git details server, clientInfo, err := extractClientServerParams() @@ -417,7 +428,7 @@ func verifyValidApiEndpoint(apiEndpoint string) error { func readParamFromEnv(envKey string, paramValue *string) error { *paramValue = getTrimmedEnv(envKey) if *paramValue == "" { - return &ErrMissingEnv{envKey} + return &ErrMissingEnv{VariableName: envKey} } return nil } diff --git a/commands/utils/params_test.go b/commands/utils/params_test.go index d2ac60420..2971eec3d 100644 --- a/commands/utils/params_test.go +++ b/commands/utils/params_test.go @@ -287,7 +287,7 @@ func TestGenerateConfigAggregatorFromEnv(t *testing.T) { jfrogXrayUrlEnv: "http://127.0.0.1:8081/xray", JFrogUserEnv: "admin", JFrogPasswordEnv: "password", - BranchNameTemplateEnv: "branch", + BranchNameTemplateEnv: "branch-${BRANCH_NAME_HASH}", CommitMessageTemplateEnv: "commit", PullRequestTitleTemplateEnv: "pr-title", InstallCommandEnv: "nuget restore", diff --git a/commands/utils/utils.go b/commands/utils/utils.go index 79d5e824a..7f7f7dc8f 100644 --- a/commands/utils/utils.go +++ b/commands/utils/utils.go @@ -174,7 +174,7 @@ func Md5Hash(values ...string) (string, error) { // Generates MD5Hash from a FixVersionMap object // The map can be returned in different order from Xray, so we need to sort the strings before hashing. -func fixVersionsMapToMd5Hash(versionsMap map[string]*VulnerabilityDetails) (string, error) { +func FixVersionsMapToMd5Hash(versionsMap map[string]*VulnerabilityDetails) (string, error) { h := crypto.MD5.New() // Sort the package names keys := make([]string, 0, len(versionsMap)) diff --git a/commands/utils/utils_test.go b/commands/utils/utils_test.go index 075787859..166ed2306 100644 --- a/commands/utils/utils_test.go +++ b/commands/utils/utils_test.go @@ -157,7 +157,7 @@ func TestFixVersionsMapToMd5Hash(t *testing.T) { } for _, test := range tests { t.Run(test.expectedHash, func(t *testing.T) { - hash, err := fixVersionsMapToMd5Hash(test.fixVersionMap) + hash, err := FixVersionsMapToMd5Hash(test.fixVersionMap) assert.NoError(t, err) assert.Equal(t, test.expectedHash, hash) }) diff --git a/go.mod b/go.mod index 56716b2a4..c9787e1ee 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/go-git/go-git/v5 v5.7.0 github.com/golang/mock v1.6.0 github.com/jfrog/build-info-go v1.9.6 - github.com/jfrog/froggit-go v1.7.3 + github.com/jfrog/froggit-go v1.8.0 github.com/jfrog/gofrog v1.3.0 github.com/jfrog/jfrog-cli-core/v2 v2.36.0 github.com/jfrog/jfrog-client-go v1.30.1 @@ -38,7 +38,7 @@ require ( github.com/emirpasic/gods v1.18.1 // indirect github.com/forPelevin/gomoji v1.1.8 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/gfleury/go-bitbucket-v1 v0.0.0-20220418082332-711d7d5e805f // indirect + github.com/gfleury/go-bitbucket-v1 v0.0.0-20230626192437-8d7be5866751 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.4.1 // indirect github.com/gocarina/gocsv v0.0.0-20230406101422-6445c2b15027 // indirect @@ -118,5 +118,3 @@ require ( // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20230518114837-fe6a826d5001 // replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20230611131847-a3b84a9004c3 - -// replace github.com/jfrog/gofrog => github.com/jfrog/gofrog v1.2.6-0.20230418122323-2bf299dd6d27 diff --git a/go.sum b/go.sum index 5c045de76..727b2a7da 100644 --- a/go.sum +++ b/go.sum @@ -104,8 +104,8 @@ github.com/forPelevin/gomoji v1.1.8/go.mod h1:8+Z3KNGkdslmeGZBC3tCrwMrcPy5GRzAD+ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gfleury/go-bitbucket-v1 v0.0.0-20220418082332-711d7d5e805f h1:xVRGXRHjGaqT9M+mNNQrsoku+p2z/+Ei/b2gs7ZCbZw= -github.com/gfleury/go-bitbucket-v1 v0.0.0-20220418082332-711d7d5e805f/go.mod h1:LB3osS9X2JMYmTzcCArHHLrndBAfcVLQAvUddfs+ONs= +github.com/gfleury/go-bitbucket-v1 v0.0.0-20230626192437-8d7be5866751 h1:Ea58sAu8LX/NS7z3aeL+YX/7+FSd9jsxq6Ecpz7QEDM= +github.com/gfleury/go-bitbucket-v1 v0.0.0-20230626192437-8d7be5866751/go.mod h1:IqOZzks2wlWCIai0esXnZPdPwxF2yOz0HcCYw5I4pCg= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= @@ -220,8 +220,8 @@ github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1R github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= github.com/jfrog/build-info-go v1.9.6 h1:lCJ2j5uXAlJsSwDe5J8WD7Co1f/hUlZvMfwfb5AzLJU= github.com/jfrog/build-info-go v1.9.6/go.mod h1:GbuFS+viHCKZYx9nWHYu7ab1DgQkFdtVN3BJPUNb2D4= -github.com/jfrog/froggit-go v1.7.3 h1:ZOXbFsdmtM6+uDbo1U+elV7sPZ4EN6grDck9aHqKynk= -github.com/jfrog/froggit-go v1.7.3/go.mod h1:xfsfQXzSaAM04RV9IyU5heBiRrsm2oS6rFCfEofQr6U= +github.com/jfrog/froggit-go v1.8.0 h1:pto08wTiPTF+B2JB9yI+fEA1A4D7cKq34p/7g8ZlTSU= +github.com/jfrog/froggit-go v1.8.0/go.mod h1:XTFf4RqWwZsF9pdERt0Ra5gO1O3iqIq7Npx2tEQSGAQ= github.com/jfrog/gofrog v1.3.0 h1:o4zgsBZE4QyDbz2M7D4K6fXPTBJht+8lE87mS9bw7Gk= github.com/jfrog/gofrog v1.3.0/go.mod h1:IFMc+V/yf7rA5WZ74CSbXe+Lgf0iApEQLxRZVzKRUR0= github.com/jfrog/jfrog-cli-core/v2 v2.36.0 h1:SRS41DL34VkCZMxdIamQ/jUhM2lI72LGxLgLV+EouNs= @@ -475,6 +475,7 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= From 16323bad24cc94663fc2c7a250938525e3d0b267 Mon Sep 17 00:00:00 2001 From: Omer Zidkoni <50792403+omerzi@users.noreply.github.com> Date: Thu, 29 Jun 2023 09:31:51 +0300 Subject: [PATCH 2/3] Fix create fixing pull request for violations (#370) --- commands/createfixpullrequests.go | 11 +- commands/createfixpullrequests_test.go | 133 +++++++++++++++++++++++++ commands/utils/git.go | 2 +- 3 files changed, 144 insertions(+), 2 deletions(-) diff --git a/commands/createfixpullrequests.go b/commands/createfixpullrequests.go index f83c9e34f..5badde684 100644 --- a/commands/createfixpullrequests.go +++ b/commands/createfixpullrequests.go @@ -320,6 +320,16 @@ func (cfp *CreateFixPullRequestsCmd) createVulnerabilitiesMap(scanResults *xrayu return nil, err } } + } else if len(scanResult.Violations) > 0 { + violations, _, _, err := xrayutils.PrepareViolations(scanResult.Violations, scanResults, isMultipleRoots, true) + if err != nil { + return nil, err + } + for i := range violations { + if err = cfp.addVulnerabilityToFixVersionsMap(&violations[i], fixVersionsMap); err != nil { + return nil, err + } + } } } return fixVersionsMap, nil @@ -345,7 +355,6 @@ func (cfp *CreateFixPullRequestsCmd) addVulnerabilityToFixVersionsMap(vulnerabil // First appearance of a version that fixes the current impacted package newVulnDetails := utils.NewVulnerabilityDetails(vulnerability, vulnFixVersion) newVulnDetails.SetIsDirectDependency(isDirectDependency) - newVulnDetails.SetCves(vulnerability.Cves) vulnerabilitiesMap[vulnerability.ImpactedDependencyName] = newVulnDetails } // Set the fixed version array to the relevant fixed version so that only that specific fixed version will be displayed diff --git a/commands/createfixpullrequests_test.go b/commands/createfixpullrequests_test.go index cb8ab22d0..d55d73524 100644 --- a/commands/createfixpullrequests_test.go +++ b/commands/createfixpullrequests_test.go @@ -2,9 +2,11 @@ package commands import ( "github.com/jfrog/jfrog-cli-core/v2/xray/formats" + xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "os" "path/filepath" "testing" @@ -171,6 +173,137 @@ func TestGetMinimalFixVersion(t *testing.T) { } } +func TestCreateVulnerabilitiesMap(t *testing.T) { + cfp := &CreateFixPullRequestsCmd{} + + testCases := []struct { + name string + scanResults *xrayutils.ExtendedScanResults + isMultipleRoots bool + expectedMap map[string]*utils.VulnerabilityDetails + }{ + { + name: "Scan results with no violations and vulnerabilities", + scanResults: &xrayutils.ExtendedScanResults{ + XrayResults: []services.ScanResponse{}, + }, + expectedMap: map[string]*utils.VulnerabilityDetails{}, + }, + { + name: "Scan results with vulnerabilities and no violations", + scanResults: &xrayutils.ExtendedScanResults{ + XrayResults: []services.ScanResponse{ + { + Vulnerabilities: []services.Vulnerability{ + { + Cves: []services.Cve{ + {Id: "CVE-2023-1234", CvssV3Score: "9.1"}, + {Id: "CVE-2023-4321", CvssV3Score: "8.9"}, + }, + Severity: "Critical", + Components: map[string]services.Component{ + "vuln1": { + FixedVersions: []string{"1.9.1", "2.0.3", "2.0.5"}, + ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "vuln1"}}}, + }, + }, + }, + { + Cves: []services.Cve{ + {Id: "CVE-2022-1234", CvssV3Score: "7.1"}, + {Id: "CVE-2022-4321", CvssV3Score: "7.9"}, + }, + Severity: "High", + Components: map[string]services.Component{ + "vuln2": { + FixedVersions: []string{"2.4.1", "2.6.3", "2.8.5"}, + ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "vuln1"}, {ComponentId: "vuln2"}}}, + }, + }, + }, + }, + }, + }, + }, + expectedMap: map[string]*utils.VulnerabilityDetails{ + "vuln1": { + FixVersion: "1.9.1", + IsDirectDependency: true, + Cves: []string{"CVE-2023-1234", "CVE-2023-4321"}, + }, + "vuln2": { + FixVersion: "2.4.1", + Cves: []string{"CVE-2022-1234", "CVE-2022-4321"}, + }, + }, + }, + { + name: "Scan results with violations and no vulnerabilities", + scanResults: &xrayutils.ExtendedScanResults{ + XrayResults: []services.ScanResponse{ + { + Violations: []services.Violation{ + { + ViolationType: "security", + Cves: []services.Cve{ + {Id: "CVE-2023-1234", CvssV3Score: "9.1"}, + {Id: "CVE-2023-4321", CvssV3Score: "8.9"}, + }, + Severity: "Critical", + Components: map[string]services.Component{ + "viol1": { + FixedVersions: []string{"1.9.1", "2.0.3", "2.0.5"}, + ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "viol1"}}}, + }, + }, + }, + { + ViolationType: "security", + Cves: []services.Cve{ + {Id: "CVE-2022-1234", CvssV3Score: "7.1"}, + {Id: "CVE-2022-4321", CvssV3Score: "7.9"}, + }, + Severity: "High", + Components: map[string]services.Component{ + "viol2": { + FixedVersions: []string{"2.4.1", "2.6.3", "2.8.5"}, + ImpactPaths: [][]services.ImpactPathNode{{{ComponentId: "root"}, {ComponentId: "viol1"}, {ComponentId: "viol2"}}}, + }, + }, + }, + }, + }, + }, + }, + expectedMap: map[string]*utils.VulnerabilityDetails{ + "viol1": { + FixVersion: "1.9.1", + IsDirectDependency: true, + Cves: []string{"CVE-2023-1234", "CVE-2023-4321"}, + }, + "viol2": { + FixVersion: "2.4.1", + Cves: []string{"CVE-2022-1234", "CVE-2022-4321"}, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + fixVersionsMap, err := cfp.createVulnerabilitiesMap(testCase.scanResults, testCase.isMultipleRoots) + assert.NoError(t, err) + for name, expectedVuln := range testCase.expectedMap { + actualVuln, exists := fixVersionsMap[name] + require.True(t, exists) + assert.Equal(t, expectedVuln.IsDirectDependency, actualVuln.IsDirectDependency) + assert.Equal(t, expectedVuln.FixVersion, actualVuln.FixVersion) + assert.ElementsMatch(t, expectedVuln.Cves, actualVuln.Cves) + } + }) + } +} + // Verifies unsupported packages return specific error // Other logic is implemented inside each package-handler. func TestUpdatePackageToFixedVersion(t *testing.T) { diff --git a/commands/utils/git.go b/commands/utils/git.go index 4259a3430..998bc5c97 100644 --- a/commands/utils/git.go +++ b/commands/utils/git.go @@ -70,7 +70,7 @@ func NewGitManager(dryRun bool, clonedRepoPath, projectPath, remoteName, token, setGoGitCustomClient() repository, err := git.PlainOpen(projectPath) if err != nil { - return nil, err + return nil, fmt.Errorf(".git folder was not found in the following path: %s. git error:\n%s", projectPath, err.Error()) } basicAuth := toBasicAuth(token, username) templates, err := loadCustomTemplates(g.CommitMessageTemplate, g.BranchNameTemplate, g.PullRequestTitleTemplate) From 1117baa27057041b2ef2b4b4718698b1bd59ebe8 Mon Sep 17 00:00:00 2001 From: Eyal Ben Moshe Date: Thu, 29 Jun 2023 15:13:32 +0300 Subject: [PATCH 3/3] README updates (#372) --- README.md | 164 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 84 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index c0ade9760..b3bdb4a08 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,6 @@ - [🤖 About JFrog Frogbot](#-about-jfrog-frogbot) - [🖥️ Installing Frogbot](#️-installing-frogbot) - [🚥 Using Frogbot](#-using-frogbot) - - [Scanning pull requests](#scanning-pull-requests) - - [Scanning repositories and fixing issues](#scanning-repositories-and-fixing-issues) - [📛 Adding the Frogbot badge](#-adding-the-frogbot-badge) - [🔥 Reporting issues](#-reporting-issues) - [💻 Contributions](#-contributions) @@ -31,26 +29,29 @@ ### Overview JFrog Frogbot is a Git bot that scans your git repositories for security vulnerabilities. -- Frogbot scans pull requests right after they are opened, but before they are merged. This unique capability ensures that the code is scanned and can be fixed even before vulnerabilities are introduced in the code base. -- Frogbot scans the Git repository periodically and creates pull requests with fixes for vulnerabilities that are detected. +1. It scans pull requests immediately after they are opened but before they are merged. This process notifies you if the pull request is about to introduce new vulnerabilities to your code. This unique capability ensures that the code is scanned and can be fixed even before vulnerabilities are introduced into the codebase. +2. It scans the Git repository periodically and creates pull requests with fixes for vulnerabilities that are detected. -Frogbot uses JFrog's vast vulnerabilities database, to which we continuously add new component vulnerability data. Also included is VulnDB, the industry's most comprehensive security database, to further extend the range of vulnerabilities detected and fixed by Frogbot. - -Frogbot supports the following Git providers: +It supports the following Git providers: - Azure Repos - Bitbucket Server - GitHub -- GitLab. +- GitLab -### What's needed for the setup? +### Why use JFrog Frogbot? +- **Software Composition Analysis (SCA)**: Scan your project dependencies for security issues. For selected security issues, get leverage-enhanced CVE data that is provided by our JFrog Security Research team. Frogbot uses JFrog's vast vulnerabilities database, to which we continuously add new component vulnerability data. Also included is VulnDB, the industry's most comprehensive security database, to further extend the range of vulnerabilities detected and fixed by Frogbot. +- **Vulnerability Contextual Analysis**: This feature uses the code context to eliminate false positive reports on vulnerable dependencies that are not applicable to the code. Vulnerability Contextual Analysis is currently supported for Python and JavaScript code. +> **_NOTE:_** **Vulnerability Contextual Analysis** require the [JFrog Advanced Security Package](https://jfrog.com/xray/). + +### What's needed for the setup? - Frogbot uses a JFrog environment to scan your Git repositories. If you don't have a JFrog environment, you can set up one for free, and use it with no limits. - Frogbot also requires a runtime environment for the scanning. The following environments are supported: - GitHub Actions - JFrog Pipelines - Jenkins - - Azure Pipelines + - Azure Pipelines ## 🖥️ Installing Frogbot @@ -100,8 +101,10 @@ After the setup is complete, you'll receive an email with your JFrog environment
## 🚥 Using Frogbot -### Scanning pull requests -#### General +
+ Scanning pull requests + +### General Frogbot uses [JFrog Xray](https://jfrog.com/xray/) (version 3.29.0 and above is required) to scan your pull requests. It adds the scan results as a comment on the pull request. If no new vulnerabilities are found, Frogbot will also add a comment, confirming this. @@ -118,134 +121,124 @@ Supported package management tools: - Poetry - Yarn 2 -#### How to use Pull Request scanning? +### How to use Pull Request scanning?
Azure Repos - After you create a new pull request, Frogbot will automatically scan it. +After you create a new pull request, Frogbot will automatically scan it. - > **_NOTE:_** The scan output will include only new vulnerabilities added by the pull request. - > Vulnerabilities that aren't new, and existed in the code before the pull request was created, will not be included in - > the - > report. In order to include all the vulnerabilities in the report, including older ones that weren't added by this - > PR, use the includeAllVulnerabilities parameter in the frogbot-config.yml file. +> **_NOTE:_** The scan output will include only new vulnerabilities added by the pull request. +> Vulnerabilities that aren't new, and existed in the code before the pull request was created, will not be included in +> the +> report. In order to include all the vulnerabilities in the report, including older ones that weren't added by this +> PR, use the includeAllVulnerabilities parameter in the frogbot-config.yml file. - The Frogbot Azure Repos scan workflow is: +The Frogbot Azure Repos scan workflow is: - 1. The developer opens a pull request. - 2. Frogbot scans the pull request and adds a comment with the scan results. - 3. Frogbot can be triggered again following new commits, by adding a comment with the `rescan` text. +1. The developer opens a pull request. +2. Frogbot scans the pull request and adds a comment with the scan results. +3. Frogbot can be triggered again following new commits, by adding a comment with the `rescan` text.
Bitbucket Server - After you create a new pull request, Frogbot will automatically scan it. +After you create a new pull request, Frogbot will automatically scan it. - > **_NOTE:_** The scan output will include only new vulnerabilities added by the pull request. - > Vulnerabilities that aren't new, and existed in the code before the pull request was created, will not be included in - > the - > report. In order to include all of the vulnerabilities in the report, including older ones that weren't added by this - > PR, use the includeAllVulnerabilities parameter in the frogbot-config.yml file. +> **_NOTE:_** The scan output will include only new vulnerabilities added by the pull request. +> Vulnerabilities that aren't new, and existed in the code before the pull request was created, will not be included in +> the +> report. In order to include all of the vulnerabilities in the report, including older ones that weren't added by this +> PR, use the includeAllVulnerabilities parameter in the frogbot-config.yml file. - The Frogbot scan on Bitbucket Server workflow: +The Frogbot scan on Bitbucket Server workflow: - 1. The developer opens a pull request. - 2. Frogbot scans the pull request and adds a comment with the scan results. - 3. Frogbot can be triggered again following new commits, by adding a comment with the `rescan` text. +1. The developer opens a pull request. +2. Frogbot scans the pull request and adds a comment with the scan results. +3. Frogbot can be triggered again following new commits, by adding a comment with the `rescan` text.
GitHub - After you create a new pull request, the maintainer of the Git repository can trigger Frogbot to scan the pull request from the pull request UI. +After you create a new pull request, the maintainer of the Git repository can trigger Frogbot to scan the pull request from the pull request UI. - > **_NOTE:_** The scan output will include only new vulnerabilities added by the pull request. - > Vulnerabilities that aren't new, and existed in the code before the pull request was created, will not be included in - > the - > report. In order to include all of the vulnerabilities in the report, including older ones that weren't added by this - > PR, use the includeAllVulnerabilities parameter in the frogbot-config.yml file. +> **_NOTE:_** The scan output will include only new vulnerabilities added by the pull request. +> Vulnerabilities that aren't new, and existed in the code before the pull request was created, will not be included in +> the +> report. In order to include all of the vulnerabilities in the report, including older ones that weren't added by this +> PR, use the includeAllVulnerabilities parameter in the frogbot-config.yml file. - The Frogbot GitHub scan workflow is: +The Frogbot GitHub scan workflow is: - 1. The developer opens a pull request. - 2. The Frogbot workflow automatically gets triggered and a [GitHub environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#creating-an-environment) named `frogbot` becomes pending for the maintainer's approval. +1. The developer opens a pull request. +2. The Frogbot workflow automatically gets triggered and a [GitHub environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#creating-an-environment) named `frogbot` becomes pending for the maintainer's approval. - [![](./images/github-pending-deployment.png)](#running-frogbot-on-github) +[![](./images/github-pending-deployment.png)](#running-frogbot-on-github) - 3. The maintainer of the repository reviews the pull request and approves the scan: [![](./images/github-deployment.gif)](#running-frogbot-on-github) - 4. Frogbot can be triggered again following new commits, by repeating steps 2 and 3. +3. The maintainer of the repository reviews the pull request and approves the scan: [![](./images/github-deployment.gif)](#running-frogbot-on-github) +4. Frogbot can be triggered again following new commits, by repeating steps 2 and 3.
GitLab - After you create a new merge request, the maintainer of the Git repository can trigger Frogbot to scan the merge request from the merge request UI. +After you create a new merge request, the maintainer of the Git repository can trigger Frogbot to scan the merge request from the merge request UI. - > **_NOTE:_** The scan output will include only new vulnerabilities added by the merge request. - > Vulnerabilities that aren't new, and existed in the code before the merge request was created, will not be included in - > the - > report. In order to include all of the vulnerabilities in the report, including older ones that weren't added by this - > merge request, use the includeAllVulnerabilities parameter in the frogbot-config.yml file. +> **_NOTE:_** The scan output will include only new vulnerabilities added by the merge request. +> Vulnerabilities that aren't new, and existed in the code before the merge request was created, will not be included in +> the +> report. In order to include all of the vulnerabilities in the report, including older ones that weren't added by this +> merge request, use the includeAllVulnerabilities parameter in the frogbot-config.yml file. - The Frogbot GitLab flow is as follows: +The Frogbot GitLab flow is as follows: - 1. The developer opens a merge request. - 2. The maintainer of the repository reviews the merge request and approves the scan by triggering the manual _frogbot-scan_ job. - 3. Frogbot is then triggered by the job, it scans the merge request, and adds a comment with the scan results. - 4. Frogbot can be triggered again following new commits, by triggering the _frogbot-scan_ job again. - [GitLab CI Run Button](./images/gitlab-run-button.png) +1. The developer opens a merge request. +2. The maintainer of the repository reviews the merge request and approves the scan by triggering the manual _frogbot-scan_ job. +3. Frogbot is then triggered by the job, it scans the merge request, and adds a comment with the scan results. +4. Frogbot can be triggered again following new commits, by triggering the _frogbot-scan_ job again. + [GitLab CI Run Button](./images/gitlab-run-button.png)
-#### 👮 Security note for pull requests scanning +### 👮 Security note for pull requests scanning When installing Frogbot using JFrog Pipelines, Jenkins and Azure DevOps, Frogbot will not wait for a maintainer's approval before scanning newly opened pull requests. Using Frogbot with these platforms, however, isn't recommended for open-source projects. When installing Frogbot using GitHub Actions and GitLab however, Frogbot will initiate the scan only after it is approved by a maintainer of the project. The goal of this review is to ensure that external code contributors don't introduce malicious code as part of the pull request. Since this review step is enforced by Frogbot when used with GitHub Actions and GitLab, it is safe to be used for open-source projects. -#### Scan results +### Scan results Frogbot adds the scan results to the pull request in the following format: -##### 👍 No issues +#### 👍 No issues If no new vulnerabilities are found, Frogbot automatically adds the following comment to the pull request: -[![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/noVulnerabilityBanner.png)](#-no-issues) +[![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/noVulnerabilityBannerPR.png)](#-no-issues) -##### 👎 Issues were found +#### 👎 Issues were found If new vulnerabilities are found, Frogbot adds them as a comment on the pull request. For example: +[![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/vulnerabilitiesBannerPR.png)](#-issues) | SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | -|:-------------------------------------------------------------------------------------------------------------------:| :----------------------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | -| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableCriticalSeverity.png)
Critical | $\color{}{\textsf{Undetermined}}$ |vconsole:3.15.0 | vconsole:3.15.0 | | +|:-------------------------------------------------------------------------------------------------------------------:| :----------------------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | | ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)
Critical | $\color{#3CB371}{\textsf{Not Applicable}}$ |minimist:1.2.5 | minimist:1.2.5 | [0.2.4]
[1.2.6] | | ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)
High | $\color{#FF7377}{\textsf{Applicable}}$ |protobufjs:6.11.2 | protobufjs:6.11.2 | [6.11.3] | | ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableHigh.png)
High | $\color{#3CB371}{\textsf{Not Applicable}}$ |lodash:4.17.19 | lodash:4.17.19 | [4.17.21] | -### Scanning repositories and fixing issues - -Frogbot scans your Git repository and automatically opens pull requests for upgrading vulnerable dependencies to a version with a fix. -![](./images/fix-pr.png) - -For GitHub repositories, Frogbot also adds [Security Alerts](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/managing-code-scanning-alerts-for-your-repository) which you can view in the GitHub UI: - -![](./images/github-code-scanning.png) - -![](./images/github-code-scanning-content.png) - -![](./images/github-code-scanning-secrets-content.png) - -![](./images/github-code-scanning-iac-content.png) +
-Frogbot uses [JFrog Xray](https://jfrog.com/xray/) for the scanning. The scanning is triggered following commits that are pushed to the repository. +
+ Scanning repositories +### Automatic pull requests creation +Frogbot scans your Git repositories periodically and automatically creates pull requests for upgrading vulnerable dependencies to a version with a fix. Supported package management tools: - Go @@ -256,9 +249,20 @@ Supported package management tools: - Poetry - Yarn 2 +![](./images/fix-pr.png) + +### Adding Security Alerts +For GitHub repositories, issues that are found during Frogbot's periodic scans are also added to the [Security Alerts](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/managing-code-scanning-alerts-for-your-repository) view in the UI. +The following alert types are supported: + +#### CVEs on vulnerable depedencies +![](./images/github-code-scanning.png) + +![](./images/github-code-scanning-content.png) +
-
+ ## 📛 Adding the Frogbot badge