Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor output writer #514

Merged
merged 53 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
b62cdee
Add Sast and Review tests
attiasas Sep 21, 2023
9fe0e79
add sast test, fix other jas
attiasas Sep 21, 2023
213885a
start adding review tests
attiasas Sep 21, 2023
bb45375
Merge remote-tracking branch 'upstream/dev' into add_sast_and_review_…
attiasas Sep 27, 2023
cf521d3
done output tests
attiasas Sep 27, 2023
9e12379
done tests implementation, start combine writer
attiasas Sep 28, 2023
e7f823a
Merge remote-tracking branch 'upstream/dev' into add_sast_and_review_…
attiasas Sep 28, 2023
55359f4
fix tests
attiasas Sep 28, 2023
9fcc04f
combine Jas output
attiasas Sep 28, 2023
e1d5a1c
combine more
attiasas Sep 28, 2023
da4f7bb
more combine
attiasas Oct 1, 2023
31ee4f4
Merge remote-tracking branch 'upstream/dev' into add_sast_and_review_…
attiasas Oct 1, 2023
a743ed6
rename subTitleDepth
attiasas Oct 3, 2023
b3f0a3c
combine more
attiasas Oct 3, 2023
96cbf22
Merge remote-tracking branch 'upstream/dev' into add_sast_and_review_…
attiasas Oct 3, 2023
3040fda
combine more
attiasas Oct 3, 2023
007822e
combine more
attiasas Oct 4, 2023
2c3ffaf
finish markdown table
attiasas Oct 4, 2023
648f50e
Merge remote-tracking branch 'upstream/dev' into add_sast_and_review_…
attiasas Oct 4, 2023
5d217ee
tidy
attiasas Oct 4, 2023
55e06f0
done combine implementations
attiasas Oct 5, 2023
b5457f1
done merge
attiasas Oct 5, 2023
25f72be
combine sast tests
attiasas Oct 5, 2023
cdfd14e
combine iac tests
attiasas Oct 5, 2023
845b917
combine applicable tests
attiasas Oct 5, 2023
a2d580c
finish combine review comment content
attiasas Oct 5, 2023
1d6d3a1
fix static test
attiasas Oct 5, 2023
1fac667
combine more, add tests for summary structure
attiasas Oct 8, 2023
7828e24
Merge remote-tracking branch 'upstream/dev' into add_sast_and_review_…
attiasas Oct 8, 2023
07fe11c
fix merge
attiasas Oct 8, 2023
6437b98
done content tests
attiasas Oct 8, 2023
ed1ed43
Merge remote-tracking branch 'upstream/dev' into add_sast_and_review_…
attiasas Oct 8, 2023
1d82c6b
add tests to comment content filter
attiasas Oct 8, 2023
35a32a6
cleanup
attiasas Oct 8, 2023
b8b4a82
fix scan all pull requests tests
attiasas Oct 9, 2023
7bd8dcd
start fix scan pull request
attiasas Oct 9, 2023
b438381
fix tests
attiasas Oct 9, 2023
6bdb0d1
Merge remote-tracking branch 'upstream/dev' into add_sast_and_review_…
attiasas Oct 9, 2023
c77c9fc
finish all output writer tests
attiasas Oct 9, 2023
fa8142c
start adjust content format, spaces and new lines
attiasas Oct 9, 2023
196b149
fix tests
attiasas Oct 9, 2023
5f2072a
fix all tests
attiasas Oct 10, 2023
8d3c082
Merge remote-tracking branch 'upstream/dev' into add_sast_and_review_…
attiasas Oct 10, 2023
8953f81
fix tests
attiasas Oct 10, 2023
e2b188d
fix tests
attiasas Oct 10, 2023
d578f57
fix pip tests
attiasas Oct 10, 2023
618bf91
fix
attiasas Oct 10, 2023
75fadb3
Merge remote-tracking branch 'upstream/dev' into add_sast_and_review_…
attiasas Oct 11, 2023
2d45ff2
fix merge
attiasas Oct 11, 2023
5d0698c
remove unused
attiasas Oct 11, 2023
ed92fd2
Merge remote-tracking branch 'upstream/dev' into add_sast_and_review_…
attiasas Oct 15, 2023
f0cce5a
review changes
attiasas Oct 16, 2023
96ca263
remove comments
attiasas Oct 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions scanpullrequest/scanallpullrequests.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"context"
"errors"
"fmt"

"github.com/jfrog/frogbot/utils"
"github.com/jfrog/frogbot/utils/outputwriter"
"github.com/jfrog/jfrog-client-go/utils/log"
"strings"

"github.com/jfrog/froggit-go/vcsclient"
)
Expand Down Expand Up @@ -64,18 +65,14 @@ func shouldScanPullRequest(repo utils.Repository, client vcsclient.VcsClient, pr

for _, comment := range pullRequestsComments {
// If this a 're-scan' request comment
if isFrogbotRescanComment(comment.Content) {
if utils.IsFrogbotRescanComment(comment.Content) {
return true, nil
}
// if this is a Frogbot 'scan results' comment and not 're-scan' request comment, do not scan this pull request.
if repo.OutputWriter.IsFrogbotResultComment(comment.Content) {
if outputwriter.IsFrogbotSummaryComment(repo.OutputWriter, comment.Content) {
return false, nil
}
}
// This is a new pull request, and it therefore should be scanned.
return true, nil
}

func isFrogbotRescanComment(comment string) bool {
return strings.Contains(strings.ToLower(strings.TrimSpace(comment)), utils.RescanRequestComment)
}
33 changes: 18 additions & 15 deletions scanpullrequest/scanallpullrequests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@ import (
"time"
)

var gitParams = &utils.Repository{
OutputWriter: &outputwriter.SimplifiedOutput{},
Params: utils.Params{
Git: utils.Git{
RepoOwner: "repo-owner",
Branches: []string{"master"},
RepoName: "repo-name",
var (
gitParams = &utils.Repository{
OutputWriter: &outputwriter.SimplifiedOutput{},
Params: utils.Params{
Git: utils.Git{
RepoOwner: "repo-owner",
Branches: []string{"master"},
RepoName: "repo-name",
},
},
},
}
}
allPrIntegrationPath = filepath.Join(outputwriter.TestMessagesDir, "integration")
)

type MockParams struct {
repoName string
Expand Down Expand Up @@ -140,13 +143,13 @@ func TestScanAllPullRequestsMultiRepo(t *testing.T) {
err := scanAllPullRequestsCmd.Run(configAggregator, client)
if assert.NoError(t, err) {
assert.Len(t, frogbotMessages, 4)
expectedMessage := "<div align='center'>\n\n[![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/vulnerabilitiesBannerPR.png)](https://github.com/jfrog/frogbot#readme)\n\n</div>\n\n\n## 📦 Vulnerable Dependencies\n\n### ✍️ Summary\n\n<div align=\"center\">\n\n| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES |\n| :---------------------: | :----------------------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/notApplicableCritical.png)<br>Critical | Not Applicable | minimist:1.2.5 | minimist:1.2.5 | [0.2.4]<br>[1.2.6] | CVE-2021-44906 |\n\n</div>\n\n## 🔬 Research Details\n\n\n**Description:**\n[Minimist](https://github.com/substack/minimist) is a simple and very popular argument parser. It is used by more than 14 million by Mar 2022. This package developers stopped developing it since April 2020 and its community released a [newer version](https://github.com/meszaros-lajos-gyorgy/minimist-lite) supported by the community.\n\n\nAn incomplete fix for [CVE-2020-7598](https://nvd.nist.gov/vuln/detail/CVE-2020-7598) partially blocked prototype pollution attacks. Researchers discovered that it does not check for constructor functions which means they can be overridden. This behavior can be triggered easily when using it insecurely (which is the common usage). For example:\n```\nvar argv = parse(['--_.concat.constructor.prototype.y', '123']);\nt.equal((function(){}).foo, undefined);\nt.equal(argv.y, undefined);\n```\nIn this example, `prototype.y` is assigned with `123` which will be derived to every newly created object. \n\nThis vulnerability can be triggered when the attacker-controlled input is parsed using Minimist without any validation. As always with prototype pollution, the impact depends on the code that follows the attack, but denial of service is almost always guaranteed.\n**Remediation:**\n##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks.\n\n\n---\n<div align=\"center\">\n\n[🐸 JFrog Frogbot](https://github.com/jfrog/frogbot#readme)\n\n</div>"
expectedMessage := outputwriter.GetOutputFromFile(t, filepath.Join(allPrIntegrationPath, "test_proj_with_vulnerability_standard.md"))
assert.Equal(t, expectedMessage, frogbotMessages[0])
expectedMessage = "<div align='center'>\n\n[![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/noVulnerabilityBannerPR.png)](https://github.com/jfrog/frogbot#readme)\n\n</div>\n\n\n---\n<div align=\"center\">\n\n[🐸 JFrog Frogbot](https://github.com/jfrog/frogbot#readme)\n\n</div>"
expectedMessage = outputwriter.GetPRSummaryContentNoIssues(t, outputwriter.TestSummaryCommentDir, true, false)
assert.Equal(t, expectedMessage, frogbotMessages[1])
expectedMessage = "<div align='center'>\n\n[![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/vulnerabilitiesBannerPR.png)](https://github.com/jfrog/frogbot#readme)\n\n</div>\n\n\n## 📦 Vulnerable Dependencies\n\n### ✍️ Summary\n\n<div align=\"center\">\n\n| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES |\n| :---------------------: | :----------------------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)<br> High | Undetermined | pip-example:1.2.3 | pyjwt:1.7.1 | [2.4.0] | CVE-2022-29217 |\n\n</div>\n\n## 🔬 Research Details\n\n\n**Description:**\n[PyJWT](https://pypi.org/project/PyJWT) is a Python implementation of the RFC 7519 standard (JSON Web Tokens). [JSON Web Tokens](https://jwt.io/) are an open, industry standard method for representing claims securely between two parties. A JWT comes with an inline signature that is meant to be verified by the receiving application. JWT supports multiple standard algorithms, and the algorithm itself is **specified in the JWT token itself**.\n\nThe PyJWT library uses the signature-verification algorithm that is specified in the JWT token (that is completely attacker-controlled), however - it requires the validating application to pass an `algorithms` kwarg that specifies the expected algorithms in order to avoid key confusion. Unfortunately - a non-default value `algorithms=jwt.algorithms.get_default_algorithms()` exists that allows all algorithms.\nThe PyJWT library also tries to mitigate key confusions in this case, by making sure that public keys are not used as an HMAC secret. For example, HMAC secrets that begin with `-----BEGIN PUBLIC KEY-----` are rejected when encoding a JWT.\n\nIt has been discovered that due to missing key-type checks, in cases where -\n1. The vulnerable application expects to receive a JWT signed with an Elliptic-Curve key (one of the algorithms `ES256`, `ES384`, `ES512`, `EdDSA`)\n2. The vulnerable application decodes the JWT token using the non-default kwarg `algorithms=jwt.algorithms.get_default_algorithms()` (or alternatively, `algorithms` contain both an HMAC-based algorithm and an EC-based algorithm)\n\nAn attacker can create an HMAC-signed (ex. `HS256`) JWT token, using the (well-known!) EC public key as the HMAC key. The validating application will accept this JWT token as a valid token.\n\nFor example, an application might have planned to validate an `EdDSA`-signed token that was generated as follows -\n```python\n# Making a good jwt token that should work by signing it with the private key\nencoded_good = jwt.encode({\"test\": 1234}, priv_key_bytes, algorithm=\"EdDSA\")\n```\nAn attacker in posession of the public key can generate an `HMAC`-signed token to confuse PyJWT - \n```python\n# Using HMAC with the public key to trick the receiver to think that the public key is a HMAC secret\nencoded_bad = jwt.encode({\"test\": 1234}, pub_key_bytes, algorithm=\"HS256\")\n```\n\nThe following vulnerable `decode` call will accept BOTH of the above tokens as valid - \n```\ndecoded = jwt.decode(encoded_good, pub_key_bytes, \nalgorithms=jwt.algorithms.get_default_algorithms())\n```\n**Remediation:**\n##### Development mitigations\n\nUse a specific algorithm instead of `jwt.algorithms.get_default_algorithms`.\nFor example, replace the following call - \n`jwt.decode(encoded_jwt, pub_key_bytes, algorithms=jwt.algorithms.get_default_algorithms())`\nWith -\n`jwt.decode(encoded_jwt, pub_key_bytes, algorithms=[\"ES256\"])`\n\n\n---\n<div align=\"center\">\n\n[🐸 JFrog Frogbot](https://github.com/jfrog/frogbot#readme)\n\n</div>"
expectedMessage = outputwriter.GetOutputFromFile(t, filepath.Join(allPrIntegrationPath, "test_proj_pip_with_vulnerability.md"))
assert.Equal(t, expectedMessage, frogbotMessages[2])
expectedMessage = "<div align='center'>\n\n[![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/noVulnerabilityBannerPR.png)](https://github.com/jfrog/frogbot#readme)\n\n</div>\n\n\n---\n<div align=\"center\">\n\n[🐸 JFrog Frogbot](https://github.com/jfrog/frogbot#readme)\n\n</div>"
expectedMessage = outputwriter.GetPRSummaryContentNoIssues(t, outputwriter.TestSummaryCommentDir, true, false)
assert.Equal(t, expectedMessage, frogbotMessages[3])
}
}
Expand Down Expand Up @@ -182,9 +185,9 @@ func TestScanAllPullRequests(t *testing.T) {
err := scanAllPullRequestsCmd.Run(paramsAggregator, client)
assert.NoError(t, err)
assert.Len(t, frogbotMessages, 2)
expectedMessage := "**🚨 Frogbot scanned this pull request and found the below:**\n\n---\n## 📦 Vulnerable Dependencies\n---\n\n### ✍️ Summary\n\n| SEVERITY | CONTEXTUAL ANALYSIS | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY | FIXED VERSIONS | CVES |\n| :---------------------: | :----------------------------------: | :----------------------------------: | :-----------------------------------: | :---------------------------------: | :---------------------------------: | \n| Critical | Not Applicable | minimist:1.2.5 | minimist:1.2.5 | [0.2.4], [1.2.6] | CVE-2021-44906 |\n\n---\n## 🔬 Research Details\n---\n\n\n#### [ CVE-2021-44906 ] minimist 1.2.5\n\n\n**Description:**\n[Minimist](https://github.com/substack/minimist) is a simple and very popular argument parser. It is used by more than 14 million by Mar 2022. This package developers stopped developing it since April 2020 and its community released a [newer version](https://github.com/meszaros-lajos-gyorgy/minimist-lite) supported by the community.\n\n\nAn incomplete fix for [CVE-2020-7598](https://nvd.nist.gov/vuln/detail/CVE-2020-7598) partially blocked prototype pollution attacks. Researchers discovered that it does not check for constructor functions which means they can be overridden. This behavior can be triggered easily when using it insecurely (which is the common usage). For example:\n```\nvar argv = parse(['--_.concat.constructor.prototype.y', '123']);\nt.equal((function(){}).foo, undefined);\nt.equal(argv.y, undefined);\n```\nIn this example, `prototype.y` is assigned with `123` which will be derived to every newly created object. \n\nThis vulnerability can be triggered when the attacker-controlled input is parsed using Minimist without any validation. As always with prototype pollution, the impact depends on the code that follows the attack, but denial of service is almost always guaranteed.\n**Remediation:**\n##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks.\n\n\n[🐸 JFrog Frogbot](https://github.com/jfrog/frogbot#readme)"
expectedMessage := outputwriter.GetOutputFromFile(t, filepath.Join(allPrIntegrationPath, "test_proj_with_vulnerability_simplified.md"))
assert.Equal(t, expectedMessage, frogbotMessages[0])
expectedMessage = "**👍 Frogbot scanned this pull request and found that it did not add vulnerable dependencies.** \n\n[🐸 JFrog Frogbot](https://github.com/jfrog/frogbot#readme)"
expectedMessage = outputwriter.GetPRSummaryContentNoIssues(t, outputwriter.TestSummaryCommentDir, true, true)
assert.Equal(t, expectedMessage, frogbotMessages[1])
}

Expand Down
5 changes: 4 additions & 1 deletion scanpullrequest/scanpullrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"context"
"errors"
"fmt"
"golang.org/x/exp/slices"
"os"

"golang.org/x/exp/slices"

"github.com/jfrog/frogbot/utils"
"github.com/jfrog/froggit-go/vcsclient"
"github.com/jfrog/froggit-go/vcsutils"
Expand Down Expand Up @@ -104,6 +105,8 @@ func scanPullRequest(repo *utils.Repository, client vcsclient.VcsClient) (err er
return
}
}

// Handle PR comments for scan output
if err = utils.HandlePullRequestCommentsAfterScan(issues, repo, client, int(pullRequestDetails.ID)); err != nil {
return
}
Expand Down
12 changes: 4 additions & 8 deletions scanpullrequest/scanpullrequest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,6 @@ func TestGetAllIssues(t *testing.T) {
EntitledForJas: true,
},
}

expectedOutput := &utils.IssuesCollection{
Vulnerabilities: []formats.VulnerabilityOrViolationRow{
{
Expand Down Expand Up @@ -809,13 +808,10 @@ func createGitLabHandler(t *testing.T, projectName string) http.HandlerFunc {
assert.NotEmpty(t, buf.String())

var expectedResponse []byte
switch {
case strings.Contains(projectName, "multi-dir"):
expectedResponse, err = os.ReadFile(filepath.Join("..", "expectedResponseMultiDir.json"))
case strings.Contains(projectName, "pip"):
expectedResponse, err = os.ReadFile(filepath.Join("..", "expectedResponsePip.json"))
default:
expectedResponse, err = os.ReadFile(filepath.Join("..", "expectedResponse.json"))
if strings.Contains(projectName, "multi-dir") {
expectedResponse = outputwriter.GetJsonBodyOutputFromFile(t, filepath.Join("..", "expected_response_multi_dir.md"))
} else {
expectedResponse = outputwriter.GetJsonBodyOutputFromFile(t, filepath.Join("..", "expected_response.md"))
}
assert.NoError(t, err)
assert.JSONEq(t, string(expectedResponse), buf.String())
Expand Down
2 changes: 1 addition & 1 deletion scanrepository/scanrepository.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ func (cfp *ScanRepositoryCmd) preparePullRequestDetails(vulnerabilitiesDetails .
return cfp.gitManager.GenerateAggregatedPullRequestTitle(cfp.projectTech), "", nil
}
vulnerabilitiesRows := utils.ExtractVulnerabilitiesDetailsToRows(vulnerabilitiesDetails)
prBody = cfp.OutputWriter.VulnerabilitiesTitle(false) + "\n" + cfp.OutputWriter.VulnerabilitiesContent(vulnerabilitiesRows) + cfp.OutputWriter.UntitledForJasMsg() + cfp.OutputWriter.Footer()
prBody = utils.GenerateFixPullRequestDetails(vulnerabilitiesRows, cfp.OutputWriter)
if cfp.aggregateFixes {
var scanHash string
if scanHash, err = utils.VulnerabilityDetailsToMD5Hash(vulnerabilitiesRows...); err != nil {
Expand Down
Loading
Loading