From d924ed49cca0fe9dedf054a065556af0fcfbf5f4 Mon Sep 17 00:00:00 2001 From: itsKedar <37594766+itsKedar@users.noreply.github.com> Date: Tue, 1 Oct 2024 09:28:30 +0530 Subject: [PATCH] Reduce PR decoration changes with configuration (#1382) --- docs/Configuration.md | 102 ++++++----- .../checkmarx/flow/config/RepoProperties.java | 9 + .../service/PullRequestCommentsHelper.java | 8 +- .../com/checkmarx/flow/utils/HTMLHelper.java | 171 ++++++++++-------- 4 files changed, 168 insertions(+), 122 deletions(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index 14c1074d5..6f5851c2c 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -837,20 +837,22 @@ github: max-description-length : max-delay : comment-update: false + zero-vulnerability-summary: true ``` -| Configuration | Default | Description | -|--------------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `webhook-token` | | Token used as a shared secret when calling the CxFlow WebHook WebService. It authenticates users for the request. GitHub signs the request with this value, and the signature is validated on the receiving end. | -| `token` | | The API token with access to the repository, with at least Read only access to the code, the ability to add comments to pull requests, and the ability to create GitHub Issues. | -| `url` | | Main repo url for GitHub | -| `api-url` | | The API endpoint for GitHub, which is a different context or potentially FQDN than the main repo url. | -| `false-positive-label` | false-positive | A label that can be defined within the GitHub Issue feedback that is used to ignore issues | -| `block-merge` | false | When triggering scans based on PullRequest, this will create a new status of pending, which will block the merge ability until the scan is complete in Checkmarx. | -| `scan-submitted-comment` | true | Comment on PullRequest with "Scan submitted (or not submitted) to Checkmarx ...". | -| `max-description-length` | 50000 | Manages number of characters to view in issue description.(value should be greater than 4 and less than 50000) | -| `max-delay` | | When Secondary rate limit is hit, it will delay each API call for issue creation(Mininum value should be 3) | -| `comment-update` | true | if false, will create a new comment for every scan | +| Configuration | Default | Description | +|------------------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `webhook-token` | | Token used as a shared secret when calling the CxFlow WebHook WebService. It authenticates users for the request. GitHub signs the request with this value, and the signature is validated on the receiving end. | +| `token` | | The API token with access to the repository, with at least Read only access to the code, the ability to add comments to pull requests, and the ability to create GitHub Issues. | +| `url` | | Main repo url for GitHub | +| `api-url` | | The API endpoint for GitHub, which is a different context or potentially FQDN than the main repo url. | +| `false-positive-label` | false-positive | A label that can be defined within the GitHub Issue feedback that is used to ignore issues | +| `block-merge` | false | When triggering scans based on PullRequest, this will create a new status of pending, which will block the merge ability until the scan is complete in Checkmarx. | +| `scan-submitted-comment` | true | Comment on PullRequest with "Scan submitted (or not submitted) to Checkmarx ...". | +| `max-description-length` | 50000 | Manages number of characters to view in issue description.(value should be greater than 4 and less than 50000) | +| `max-delay` | | When Secondary rate limit is hit, it will delay each API call for issue creation(Mininum value should be 3) | +| `comment-update` | true | if false, will create a new comment for every scan | +| `zero-vulnerability-summary` | false | if true, will not comment in PR decoration any details for scans as vulnerabilities are zero. | **Note**: A service account is required with access to the repositories that will be scanned, pull requests that will be commented on, and GitHub issues that will be created/updated. ### GitLab @@ -863,18 +865,20 @@ gitlab: false-positive-label: false-positive block-merge: true comment-update: false + zero-vulnerability-summary: true ``` -| Configuration | Default | Description | -|--------------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `webhook-token` | | Token used as a shared secret when calling the CxFlow WebHook WebService. It authenticates users for the request. | -| `token` | | This is the API token with access to the repository, with at least Read only access to code, the ability to add comments to pull requests, and the ability to create GitLab issues. | -| `url` | | Main repo url for GitLab. | -| `api-url` | | The API endpoint for GitLab, which serves a different context or potential FQDN than the main repo url. | -| `false-positive-label` | false-positive | A label that can be defined within the GitLab Issue feedback to ignore issues | -| `block-merge` | false | When triggering scans based on Merge Request, the Merge request is marked as WIP in GitLab, which blocks the merge ability until the scan is complete in Checkmarx. | -| `scan-submitted-comment` | true | Comment on Merge Request with "Scan submitted (or not submitted) to Checkmarx ...". | -| `comment-update` | true | if false, will create a new comment for every scan | +| Configuration | Default | Description | +|------------------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `webhook-token` | | Token used as a shared secret when calling the CxFlow WebHook WebService. It authenticates users for the request. | +| `token` | | This is the API token with access to the repository, with at least Read only access to code, the ability to add comments to pull requests, and the ability to create GitLab issues. | +| `url` | | Main repo url for GitLab. | +| `api-url` | | The API endpoint for GitLab, which serves a different context or potential FQDN than the main repo url. | +| `false-positive-label` | false-positive | A label that can be defined within the GitLab Issue feedback to ignore issues | +| `block-merge` | false | When triggering scans based on Merge Request, the Merge request is marked as WIP in GitLab, which blocks the merge ability until the scan is complete in Checkmarx. | +| `scan-submitted-comment` | true | Comment on Merge Request with "Scan submitted (or not submitted) to Checkmarx ...". | +| `comment-update` | true | if false, will create a new comment for every scan | +| `zero-vulnerability-summary` | false | if true, will not comment in PR decoration any details for scans as vulnerabilities are zero. | **Note**: A service account is required with access to the repositories that are going to be scanned, pull requests that are commented on, and GitLab issues that are created/updated. @@ -890,25 +894,26 @@ azure: block-merge: true closed-status: Closed open-status: Active + zero-vulnerability-summary: true ``` -| Configuration | Default | Description | -|------------------------|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `webhook-token` | | **:** as defined when registering the event in ADO. Used as a shared secret when calling the CxFlow WebHook WebService. It authenticates users for the request. | -| `token` | | This is the API token with access to the repository. It has at least Read only access to code, the ability to add comments to pull requests, and the ability to create Azure WorkItems. | -| `url` | | Main repo url for Azure DevOps, including high level namespace. **Note**: this is only required when running from the command line and not for WebHooks. | -| `issue-type` | issue | The WorkItem type within Azure, i.e. issue / impediment. | -| `issue-body` | description | The body to enter free text regarding the issue. The default across various workItem types are **Description** or **System.Description**. | -| `app-tag-prefix` | app | Used for tracking existing issues. Issues are tagged with this value, if **app** is provided (without namespace/repo/branch) | -| `owner-tag-prefix` | owner | Used for tracking existing issues. Issues are tagged with this value | -| `repo-tag-prefix` | repo | Used for tracking existing issues. Issues are tagged with this value | -| `branch-label-prefix` | branch | Used for tracking existing issues. Issues are tagged with this value | -| `api-version` | 5.0 | Azure DevOps API version to use | -| `open-status` | | Status when re-opening a a workItem | -| `closed-status` | | Status when closing a workItem | -| `false-positive-label` | false-positive | A label/tag that can be defined within the Azure Issue feedback being used to ignore issues. | -| `block-merge` | false | When triggering scans is based on pull request, this marks the Pull in blocked state until the scan is complete at Checkmarx. | - +| Configuration | Default | Description | +|------------------------------|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `webhook-token` | | **:** as defined when registering the event in ADO. Used as a shared secret when calling the CxFlow WebHook WebService. It authenticates users for the request. | +| `token` | | This is the API token with access to the repository. It has at least Read only access to code, the ability to add comments to pull requests, and the ability to create Azure WorkItems. | +| `url` | | Main repo url for Azure DevOps, including high level namespace. **Note**: this is only required when running from the command line and not for WebHooks. | +| `issue-type` | issue | The WorkItem type within Azure, i.e. issue / impediment. | +| `issue-body` | description | The body to enter free text regarding the issue. The default across various workItem types are **Description** or **System.Description**. | +| `app-tag-prefix` | app | Used for tracking existing issues. Issues are tagged with this value, if **app** is provided (without namespace/repo/branch) | +| `owner-tag-prefix` | owner | Used for tracking existing issues. Issues are tagged with this value | +| `repo-tag-prefix` | repo | Used for tracking existing issues. Issues are tagged with this value | +| `branch-label-prefix` | branch | Used for tracking existing issues. Issues are tagged with this value | +| `api-version` | 5.0 | Azure DevOps API version to use | +| `open-status` | | Status when re-opening a a workItem | +| `closed-status` | | Status when closing a workItem | +| `false-positive-label` | false-positive | A label/tag that can be defined within the Azure Issue feedback being used to ignore issues. | +| `block-merge` | false | When triggering scans is based on pull request, this marks the Pull in blocked state until the scan is complete at Checkmarx. | +| `zero-vulnerability-summary` | false | if true, will not comment in PR decoration any details for scans as vulnerabilities are zero. | **Note**: A service account is required with access to the repositories that are scanned, pull requests that are commented on, and Azure WorkItems that are created/updated. ### Bitbucket (Cloud and Server) @@ -919,17 +924,18 @@ bitbucket: url: http://bitbucket.org api-url: http://api.bitbucket.org api-path: /2.0 + zero-vulnerability-summary: true ``` -| Configuration | Default | Description | -|--------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `webhook-token` | | Token used as a shared secret when calling the CxFlow WebHook WebService. It authenticates users for the request. The Bitbucket cloud does not allow for a shared secret, therefore a URL parameter called token, must be provided in this case. | -| `token` | | This is the API token with access to the repository with at least Read only access to code and the ability to add comments to pull requests. BitBucket requires the **:** format in the configuration.
`userid:app password`(Format while using BitBucket Cloud)
`userid:password`(Format while using BitBucket Server) | -| `api-url` | | - [https://api.bitbucket.org](https://api.bitbucket.org) (URL for the Cloud BitBucket)
- [https://api.companyxyzbitbucket](https://api.companyxyzbitbucket) (URL for the BitBucket server is just the server hostname with `api.` prefixed) | -| `url` | | - [https://bitbucket.org](https://api.bitbucket.org) (URL for the Cloud BitBucket)
- [https://companyxyzbitbucket](https://api.companyxyzbitbucket)(URL for the BitBucket server is just the server hostname) | -| `api-path` | | The API URL path (appended to the URL) for BitBucket | -| 'scan-submitted-comment` | true | Comment on Merge Request with "Scan submitted (or not submitted) to Checkmarx ...". | - +| Configuration | Default | Description | +|------------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `webhook-token` | | Token used as a shared secret when calling the CxFlow WebHook WebService. It authenticates users for the request. The Bitbucket cloud does not allow for a shared secret, therefore a URL parameter called token, must be provided in this case. | +| `token` | | This is the API token with access to the repository with at least Read only access to code and the ability to add comments to pull requests. BitBucket requires the **:** format in the configuration.
`userid:app password`(Format while using BitBucket Cloud)
`userid:password`(Format while using BitBucket Server) | +| `api-url` | | - [https://api.bitbucket.org](https://api.bitbucket.org) (URL for the Cloud BitBucket)
- [https://api.companyxyzbitbucket](https://api.companyxyzbitbucket) (URL for the BitBucket server is just the server hostname with `api.` prefixed) | +| `url` | | - [https://bitbucket.org](https://api.bitbucket.org) (URL for the Cloud BitBucket)
- [https://companyxyzbitbucket](https://api.companyxyzbitbucket)(URL for the BitBucket server is just the server hostname) | +| `api-path` | | The API URL path (appended to the URL) for BitBucket | +| 'scan-submitted-comment` | true | Comment on Merge Request with "Scan submitted (or not submitted) to Checkmarx ...". | +| `zero-vulnerability-summary` | false | if true, will not comment in PR decoration any details for scans as vulnerabilities are zero. | **Note**: As mentioned in the prerequisites, a service account is required that has appropriate access to the repositories that will be scanned, pull requests that will be commented on, GitHub issues that will be created/updated. ## JSON Config Override diff --git a/src/main/java/com/checkmarx/flow/config/RepoProperties.java b/src/main/java/com/checkmarx/flow/config/RepoProperties.java index a1293240f..26c009935 100644 --- a/src/main/java/com/checkmarx/flow/config/RepoProperties.java +++ b/src/main/java/com/checkmarx/flow/config/RepoProperties.java @@ -26,6 +26,7 @@ public class RepoProperties { private String flowSummaryHeader = PullRequestCommentsHelper.COMMENT_TYPE_SAST_FINDINGS_2; private boolean cxSummary = true; private boolean cxTableSummary = false; + private boolean zeroVulnerabilitySummary = false; private String cxSummaryHeader = "Checkmarx Scan Summary"; private Map optionalInstances; private boolean scanSubmittedComment = true; @@ -188,6 +189,14 @@ public String getCxSummaryHeader() { return cxSummaryHeader; } + public boolean isZeroVulnerabilitySummary() { + return zeroVulnerabilitySummary; + } + + public void setZeroVulnerabilitySummary(boolean zeroVulnerabilitySummary) { + this.zeroVulnerabilitySummary = zeroVulnerabilitySummary; + } + public void setCxSummaryHeader(String cxSummaryHeader) { this.cxSummaryHeader = cxSummaryHeader; } diff --git a/src/main/java/com/checkmarx/flow/service/PullRequestCommentsHelper.java b/src/main/java/com/checkmarx/flow/service/PullRequestCommentsHelper.java index 39979b687..cc0b6ddf7 100644 --- a/src/main/java/com/checkmarx/flow/service/PullRequestCommentsHelper.java +++ b/src/main/java/com/checkmarx/flow/service/PullRequestCommentsHelper.java @@ -23,6 +23,8 @@ public class PullRequestCommentsHelper { private static final String COMMENT_TYPE_SCAN_FAILED_MESSAGE = "Scan failed due to some error."; + private static final String ZERO_SAST_VULNERABILITY_COMMENT ="No SAST Vulnerability Found!!"; + private static final String ZERO_SCA_VULNERABILITY_COMMENT ="No SCA Vulnerability Found!!"; public static boolean isCheckMarxComment(RepoComment comment) { String currentComment = comment.getComment(); return currentComment.contains(COMMENT_TYPE_SAST_FINDINGS_2) && currentComment.contains(COMMENT_TYPE_SAST_FINDINGS_1) || @@ -119,9 +121,9 @@ public static boolean shouldUpdateComment(String newComment, String oldComment) enum CommentType { SCAN_STARTED(Arrays.asList(COMMENT_TYPE_SAST_SCAN_STARTED)), - SAST_FINDINGS(Arrays.asList(COMMENT_TYPE_SAST_FINDINGS_1, COMMENT_TYPE_SAST_FINDINGS_2)), - SCA(Arrays.asList(COMMENT_TYPE_SCA_FINDINGS)), - SCA_AND_SAST(Arrays.asList(COMMENT_TYPE_SAST_FINDINGS_1, COMMENT_TYPE_SAST_FINDINGS_2, COMMENT_TYPE_SCA_FINDINGS)), + SAST_FINDINGS(Arrays.asList(COMMENT_TYPE_SAST_FINDINGS_1, COMMENT_TYPE_SAST_FINDINGS_2,ZERO_SAST_VULNERABILITY_COMMENT)), + SCA(Arrays.asList(COMMENT_TYPE_SCA_FINDINGS,ZERO_SCA_VULNERABILITY_COMMENT)), + SCA_AND_SAST(Arrays.asList(COMMENT_TYPE_SAST_FINDINGS_1, COMMENT_TYPE_SAST_FINDINGS_2, COMMENT_TYPE_SCA_FINDINGS ,ZERO_SAST_VULNERABILITY_COMMENT,ZERO_SCA_VULNERABILITY_COMMENT)), SCAN_FAILED_MESSAGE(Arrays.asList(COMMENT_TYPE_SCAN_FAILED_MESSAGE)), SCAN_NOT_SUBMITTED(Arrays.asList(COMMENT_TYPE_SAST_SCAN_NOT_SUBMITTED)); diff --git a/src/main/java/com/checkmarx/flow/utils/HTMLHelper.java b/src/main/java/com/checkmarx/flow/utils/HTMLHelper.java index 2c6cda9d7..e126c13bd 100644 --- a/src/main/java/com/checkmarx/flow/utils/HTMLHelper.java +++ b/src/main/java/com/checkmarx/flow/utils/HTMLHelper.java @@ -7,7 +7,7 @@ import com.checkmarx.flow.custom.IssueTracker; import com.checkmarx.flow.dto.ScanRequest; import com.checkmarx.sdk.config.Constants; -import com.checkmarx.sdk.dto.sast.Filter.Severity; +import com.checkmarx.sdk.dto.sast.Filter.Severity; import com.checkmarx.sdk.dto.ScanResults; import com.checkmarx.sdk.dto.sca.SCAResults; import com.checkmarx.sdk.dto.cx.CxScanSummary; @@ -64,12 +64,50 @@ public static String getMergeCommentMD(ScanRequest request, ScanResults results, ScanUtils.setASTXIssuesInScanResults(results); } - addScanSummarySection(request, results, properties, body); - addFlowSummarySection(results, properties, body, request); - addDetailsSection(request, results, properties, body); + if (properties.isZeroVulnerabilitySummary()) { + //SAST vulnerability check + if (results.getScanSummary().getHighSeverity() == 0 && + results.getScanSummary().getMediumSeverity() == 0 && + results.getScanSummary().getLowSeverity() == 0 && + results.getScanSummary().getInfoSeverity() == 0) { + //SAST version check + if (request.getSastVersion() >= 9.7) { + //if 9.7 and above critical vulnerability check + if (results.getScanSummary().getCriticalSeverity() == 0) { + appendAll(body,MarkDownHelper.getBoldText("No SAST Vulnerability Found!!")); + } + } else { + appendAll(body,MarkDownHelper.getBoldText("No SAST Vulnerability Found!!")); + } + } else { + //Add Vulnerability details if vulnerabilities found + addScanSummarySection(request, results, properties, body); + addFlowSummarySection(results, properties, body, request); + addDetailsSection(request, results, properties, body); + } + } else { + addScanSummarySection(request, results, properties, body); + addFlowSummarySection(results, properties, body, request); + addDetailsSection(request, results, properties, body); + } + } + + //SCA vulnerability check + if (properties.isZeroVulnerabilitySummary()) { + Optional.ofNullable(results.getScaResults()).ifPresent(r -> { + if (r.getSummary().getFindingCounts().get(Severity.valueOf("CRITICAL")) == 0 && + r.getSummary().getFindingCounts().get(Severity.valueOf("HIGH")) == 0 && + r.getSummary().getFindingCounts().get(Severity.valueOf("MEDIUM")) == 0 && + r.getSummary().getFindingCounts().get(Severity.valueOf("LOW")) == 0) { + appendAll(body,MarkDownHelper.getBoldText("No SCA Vulnerability Found!!")); + } else { + addScaBody(results, body, request); + } + }); + }else{ + addScaBody(results, body, request); } - addScaBody(results, body, request); return body.toString(); } @@ -103,22 +141,22 @@ public static String getScanRequestIssueKeyWithDefaultProductValue(ScanRequest s return xIssueKeyValue; } - public static String getScanRequestIssueKeyWithDefaultProductValue(ScanRequest scanRequest, String titleToConcat,String labelPrefix) { + public static String getScanRequestIssueKeyWithDefaultProductValue(ScanRequest scanRequest, String titleToConcat, String labelPrefix) { String productPrefix; - if(labelPrefix != null){ + if (labelPrefix != null) { productPrefix = labelPrefix; - } else{ + } else { productPrefix = scanRequest.getProduct().getProduct(); } - if(!titleToConcat.startsWith(productPrefix)) { + if (!titleToConcat.startsWith(productPrefix)) { titleToConcat = productPrefix + " " + titleToConcat; } return titleToConcat; } private static void addFlowSummarySection(ScanResults results, RepoProperties properties, StringBuilder body, ScanRequest request) { - if (properties.isFlowSummary() ) { + if (properties.isFlowSummary()) { if (!ScanUtils.empty(properties.getFlowSummaryHeader())) { appendAll(body, MarkDownHelper.getMdHeaderType(3, properties.getFlowSummaryHeader()), CRLF); } @@ -139,6 +177,7 @@ private static void addFlowSummarySection(ScanResults results, RepoProperties pr } } } + private static void addScaBody(ScanResults results, StringBuilder body, ScanRequest request) { Optional.ofNullable(results.getScaResults()).ifPresent(r -> { log.debug("Building merge comment MD for SCA scanner"); @@ -159,7 +198,7 @@ private static void addScaBody(ScanResults results, StringBuilder body, ScanRequ } public static String getMDBody(ScanResults.XIssue issue, String branch, String fileUrl, - FlowProperties flowProperties, int max_desc_length) { + FlowProperties flowProperties, int max_desc_length) { StringBuilder body = new StringBuilder(); List scaDetails = issue.getScaDetails(); @@ -167,7 +206,7 @@ public static String getMDBody(ScanResults.XIssue issue, String branch, String f setSCAMDBody(branch, body, scaDetails); } else { - setSASTMDBody(issue, branch, fileUrl, flowProperties, body,max_desc_length); + setSASTMDBody(issue, branch, fileUrl, flowProperties, body, max_desc_length); } return body.toString(); @@ -178,9 +217,9 @@ private static void scaSummaryBuilder(StringBuilder body, SCAResults r, ScanRequ appendAll(body, MarkDownHelper.getBoldText("Total Packages Identified"), ": ", MarkDownHelper.getBoldText(String.valueOf(r.getSummary().getTotalPackages())), CRLF); appendAll(body, MarkDownHelper.getBoldText("Scan Risk Score"), ": ", MarkDownHelper.getBoldText(String.format("%.2f", r.getSummary().getRiskScore())), CRLF, CRLF); - Arrays.asList("Critical","High", "Medium", "Low").forEach(v -> + Arrays.asList("Critical", "High", "Medium", "Low").forEach(v -> appendAll(body, MarkDownHelper.getSeverityIconFromLinkByText(v, request), MarkDownHelper.getNonBreakingSpace(request), MarkDownHelper.getBoldText(String.valueOf(r.getSummary().getFindingCounts().get(Severity.valueOf(v.toUpperCase())))), - " ", MarkDownHelper.getBoldText(v), " " ,MarkDownHelper.getBoldText("severity vulnerabilities"), CRLF)); + " ", MarkDownHelper.getBoldText(v), " ", MarkDownHelper.getBoldText("severity vulnerabilities"), CRLF)); appendAll(body, MarkDownHelper.getTextLink(MarkDownHelper.MORE_DETAILS_LINK_HEADER, r.getWebReportLink()), CRLF); } @@ -194,20 +233,20 @@ private static void scaVulnerabilitiesTableBuilder(StringBuilder body, SCAResult r.getFindings().stream().sorted(Comparator.comparingDouble(o -> -o.getScore())) .sorted(Comparator.comparingInt(o -> -o.getSeverity().ordinal())).forEach( - f -> MarkDownHelper.appendMDtableRow(body, '`' + f.getId() + '`', extractPackageNameFromFindings(r, f), - f.getSeverity().name(), - // "N\\A", - String.valueOf(f.getScore()), f.getPublishDate(), - extractPackageVersionFromFindings(r, f), - Optional.ofNullable(f.getFixResolutionText()) - .filter(text -> !text.isEmpty()) - .orElse("No Recommendations"), - " [Vulnerability Link](" - + ScanUtils.constructVulnerabilityUrl(r.getWebReportLink(), f) + ")", - (StringUtils.isEmpty(f.getCveName())) ? "N\\A" - : appendAll(new StringBuilder(), '[', f.getCveName(), - "](https://nvd.nist.gov/vuln/detail/", f.getCveName(), ")") - .toString())); + f -> MarkDownHelper.appendMDtableRow(body, '`' + f.getId() + '`', extractPackageNameFromFindings(r, f), + f.getSeverity().name(), + // "N\\A", + String.valueOf(f.getScore()), f.getPublishDate(), + extractPackageVersionFromFindings(r, f), + Optional.ofNullable(f.getFixResolutionText()) + .filter(text -> !text.isEmpty()) + .orElse("No Recommendations"), + " [Vulnerability Link](" + + ScanUtils.constructVulnerabilityUrl(r.getWebReportLink(), f) + ")", + (StringUtils.isEmpty(f.getCveName())) ? "N\\A" + : appendAll(new StringBuilder(), '[', f.getCveName(), + "](https://nvd.nist.gov/vuln/detail/", f.getCveName(), ")") + .toString())); appendAll(body, "", CRLF); } @@ -221,8 +260,7 @@ private static String extractPackageVersionFromFindings(SCAResults r, Finding f) try { return r.getPackages().stream().filter(p -> p.getId().equals(f.getPackageId())).map(Package::getVersion) .findFirst().orElse(""); - } - catch (Exception e) { + } catch (Exception e) { log.warn("failed to extract package version from SCA finding - {}", f.toString()); return "EMPTY"; } @@ -260,7 +298,7 @@ private static void setSCAMDBody(String branch, StringBuilder body, List trueIssues = issue.getDetails().entrySet().stream() .filter(x -> x.getKey() != null && x.getValue() != null && !x.getValue().isFalsePositive()) @@ -349,7 +387,7 @@ private static void appendLinesHTML(StringBuilder body, Map fpIssues) { + Map fpIssues) { if (flowProperties.isListFalsePositives() && !fpIssues.isEmpty()) {// List the false positives / not exploitable body.append("
Lines Marked Not Exploitable: "); for (Map.Entry entry : fpIssues.entrySet()) { @@ -365,7 +403,7 @@ private static void setSCAHtmlBody(ScanResults.XIssue issue, ScanRequest request body.append(ITALIC_OPENING_DIV).append(any.getFinding().getDescription()).append(ITALIC_CLOSING_DIV) .append(MarkDownHelper.getLineBreak(request)); body.append(String.format(SCATicketingConstants.SCA_HTML_ISSUE_BODY, any.getFinding().getSeverity(), - any.getVulnerabilityPackage().getName(), request.getBranch())).append(DIV_CLOSING_TAG) + any.getVulnerabilityPackage().getName(), request.getBranch())).append(DIV_CLOSING_TAG) .append(MarkDownHelper.getLineBreak(request)); }); @@ -395,31 +433,24 @@ private static void setSCAHtmlBody(ScanResults.XIssue issue, ScanRequest request } private static void setSASTMDBody(ScanResults.XIssue issue, String branch, String fileUrl, - FlowProperties flowProperties, StringBuilder body, int max_desc_length) { + FlowProperties flowProperties, StringBuilder body, int max_desc_length) { log.debug("Building MD body for SAST scanner"); body.append(String.format(ISSUE_BODY, issue.getVulnerability(), issue.getFilename(), branch)).append(CRLF) .append(CRLF); - if (!ScanUtils.empty(issue.getDescription())) - { - if(flowProperties.getBugTracker().equalsIgnoreCase("GitHub")) - { - if(max_desc_length<4 || max_desc_length>50000) - { + if (!ScanUtils.empty(issue.getDescription())) { + if (flowProperties.getBugTracker().equalsIgnoreCase("GitHub")) { + if (max_desc_length < 4 || max_desc_length > 50000) { log.info("max description length should be greater than 4 and less than 50000"); - body.append("*").append(StringUtils.abbreviate(issue.getDescription(),50000 )).append("*").append(CRLF).append(CRLF); - } - else - { - body.append("*").append(StringUtils.abbreviate(issue.getDescription(),max_desc_length )).append("*").append(CRLF).append(CRLF); + body.append("*").append(StringUtils.abbreviate(issue.getDescription(), 50000)).append("*").append(CRLF).append(CRLF); + } else { + body.append("*").append(StringUtils.abbreviate(issue.getDescription(), max_desc_length)).append("*").append(CRLF).append(CRLF); } - } - else - { + } else { body.append("*").append(issue.getDescription()).append("*").append(CRLF).append(CRLF); } } - if(!ScanUtils.empty(issue.getSimilarityId())){ + if (!ScanUtils.empty(issue.getSimilarityId())) { body.append("Similarity Id").append(": ").append(issue.getSimilarityId()).append(CRLF).append(CRLF); } if (!ScanUtils.empty(issue.getSeverity())) { @@ -457,7 +488,7 @@ private static void setSASTMDBody(ScanResults.XIssue issue, String branch, Strin } private static void appendSastAstDetails(ScanResults.XIssue issue, String fileUrl, FlowProperties flowProperties, - StringBuilder body) { + StringBuilder body) { if (issue.getDetails() != null && !issue.getDetails().isEmpty()) { Map trueIssues = issue.getDetails().entrySet().stream() .filter(x -> x.getKey() != null && x.getValue() != null && !x.getValue().isFalsePositive()) @@ -474,14 +505,14 @@ private static void appendSastAstDetails(ScanResults.XIssue issue, String fileUr } private static void appendLines(String fileUrl, StringBuilder body, - Map trueIssues) { + Map trueIssues) { if (!trueIssues.isEmpty()) { body.append("Lines: "); for (Map.Entry entry : trueIssues.entrySet()) { if (fileUrl != null) { // []() appendAll(body, "[", entry.getKey(), "](", fileUrl, "#L", entry.getKey(), ") "); } else { // if the fileUrl is not provided, simply putting the line number (no link) - - // ADO for example + // ADO for example appendAll(body, entry.getKey(), " "); } } @@ -490,7 +521,7 @@ private static void appendLines(String fileUrl, StringBuilder body, } private static void appendCodeSnippet(String fileUrl, StringBuilder body, - Map trueIssues) { + Map trueIssues) { for (Map.Entry entry : trueIssues.entrySet()) { if (entry.getValue() != null && entry.getValue().getCodeSnippet() != null) { appendAll(body, "---", CRLF); @@ -503,7 +534,7 @@ private static void appendCodeSnippet(String fileUrl, StringBuilder body, } private static void appendNotExploitable(String fileUrl, FlowProperties flowProperties, StringBuilder body, - Map fpIssues) { + Map fpIssues) { if (flowProperties.isListFalsePositives() && !fpIssues.isEmpty()) {// List the false positives / not exploitable body.append(CRLF); body.append("Lines Marked Not Exploitable: "); @@ -511,7 +542,7 @@ private static void appendNotExploitable(String fileUrl, FlowProperties flowProp if (fileUrl != null) { // []() appendAll(body, "[", entry.getKey(), "](", fileUrl, "#L", entry.getKey(), ") "); } else { // if the fileUrl is not provided, simply putting the line number (no link) - - // ADO for example + // ADO for example appendAll(body, entry.getKey(), " "); } } @@ -549,7 +580,7 @@ public static String getTextBody(ScanResults.XIssue issue, ScanRequest request, } body.append(CRLF); if (!ScanUtils.empty(issue.getSeverity())) { - appendAll(body, SEVERITY, ": ", issue.getSeverity(), CRLF); + appendAll(body, SEVERITY, ": ", issue.getSeverity(), CRLF); } appendCWE(issue, flowProperties, body); @@ -590,7 +621,7 @@ private static void appendCWE(ScanResults.XIssue issue, FlowProperties flowPrope } private static void appendSastAstDetials(ScanResults.XIssue issue, FlowProperties flowProperties, - StringBuilder body) { + StringBuilder body) { if (issue.getDetails() != null && !issue.getDetails().isEmpty()) { Map trueIssues = issue.getDetails().entrySet().stream() .filter(x -> x.getKey() != null && x.getValue() != null && !x.getValue().isFalsePositive()) @@ -603,7 +634,7 @@ private static void appendSastAstDetials(ScanResults.XIssue issue, FlowPropertie trueIssues.keySet().forEach(key -> body.append(key).append(" ")); } if (flowProperties.isListFalsePositives() && !fpIssues.isEmpty()) {// List the false positives / not - // exploitable + // exploitable body.append("Lines Marked Not Exploitable: "); fpIssues.keySet().forEach(key -> body.append(key).append(" ")); } @@ -631,7 +662,7 @@ private static void appendOsaDetails(StringBuilder body, ScanResults.OsaDetails addIfPresent.accept(URL, o.getUrl()); } - + private static void addSastAstDetailsBody(ScanRequest request, StringBuilder body, Map xMap, Comparator issueComparator) { xMap.entrySet().stream() .filter(x -> x.getValue() != null && x.getValue().getDetails() != null) @@ -640,12 +671,11 @@ private static void addSastAstDetailsBody(ScanRequest request, StringBuilder bod ScanResults.XIssue currentIssue = xIssue.getValue(); String newFileName; String fileUrl; - if(currentIssue.getFilename().contains(" ")) { + if (currentIssue.getFilename().contains(" ")) { newFileName = currentIssue.getFilename().replace(" ", "%20"); fileUrl = ScanUtils.getFileUrl(request, newFileName); - } - else { - fileUrl = ScanUtils.getFileUrl(request,currentIssue.getFilename() ); + } else { + fileUrl = ScanUtils.getFileUrl(request, currentIssue.getFilename()); } currentIssue.getDetails().entrySet().stream() .filter(x -> x.getKey() != null && x.getValue() != null && !x.getValue().isFalsePositive()) @@ -713,8 +743,7 @@ private static void addOsaDetailesBody(ScanResults results, StringBuilder body, private static void addScanSummarySection(ScanRequest request, ScanResults results, RepoProperties properties, StringBuilder body) { setScannerLogoHeader(request, results, body); CxScanSummary summary = results.getScanSummary(); - if(properties.isCxSummary()) - { + if (properties.isCxSummary()) { setScannerSummaryHeader(results, body); setScannerTotalVulnerabilities(body, summary, request); } @@ -723,13 +752,13 @@ private static void addScanSummarySection(ScanRequest request, ScanResults resul appendAll(body, MarkDownHelper.getMdHeaderType(4, properties.getCxSummaryHeader()), CRLF); } MarkDownHelper.appendMDtableHeaders(body, SEVERITY, "Count"); - if(request.getSastVersion()>=9.7){ + if (request.getSastVersion() >= 9.7) { MarkDownHelper.appendMDtableRow(body, "Critical", summary.getCriticalSeverity().toString()); MarkDownHelper.appendMDtableRow(body, "High", summary.getHighSeverity().toString()); MarkDownHelper.appendMDtableRow(body, "Medium", summary.getMediumSeverity().toString()); MarkDownHelper.appendMDtableRow(body, "Low", summary.getLowSeverity().toString()); MarkDownHelper.appendMDtableRow(body, "Informational", summary.getInfoSeverity().toString()); - }else{ + } else { MarkDownHelper.appendMDtableRow(body, "High", summary.getHighSeverity().toString()); MarkDownHelper.appendMDtableRow(body, "Medium", summary.getMediumSeverity().toString()); MarkDownHelper.appendMDtableRow(body, "Low", summary.getLowSeverity().toString()); @@ -742,13 +771,13 @@ private static void addScanSummarySection(ScanRequest request, ScanResults resul private static void setScannerTotalVulnerabilities(StringBuilder body, CxScanSummary summary, ScanRequest request) { appendAll(body, "Total of " + countSastTotalVulnerabilities(summary) + " vulnerabilities", MarkDownHelper.getLineBreak(request)); - if(request.getSastVersion()>=9.7){ + if (request.getSastVersion() >= 9.7) { appendAll(body, MarkDownHelper.getCriticalIconFromLink(request), MarkDownHelper.getNonBreakingSpace(request), MarkDownHelper.getBoldText(summary.getCriticalSeverity() + " Critical"), MarkDownHelper.getLineBreak(request)); appendAll(body, MarkDownHelper.getHighIconFromLink(request), MarkDownHelper.getNonBreakingSpace(request), MarkDownHelper.getBoldText(summary.getHighSeverity() + " High"), MarkDownHelper.getLineBreak(request)); appendAll(body, MarkDownHelper.getMediumIconFromLink(request), MarkDownHelper.getNonBreakingSpace(request), MarkDownHelper.getBoldText(summary.getMediumSeverity() + " Medium"), MarkDownHelper.getLineBreak(request)); appendAll(body, MarkDownHelper.getLowIconFromLink(request), MarkDownHelper.getNonBreakingSpace(request), MarkDownHelper.getBoldText(summary.getLowSeverity() + " Low"), MarkDownHelper.getLineBreak(request)); appendAll(body, MarkDownHelper.getInfoIconFromLink(request), MarkDownHelper.getNonBreakingSpace(request), MarkDownHelper.getBoldText(summary.getInfoSeverity() + " Info"), MarkDownHelper.getLineBreak(request), CRLF); - }else { + } else { appendAll(body, MarkDownHelper.getHighIconFromLink(request), MarkDownHelper.getNonBreakingSpace(request), MarkDownHelper.getBoldText(summary.getHighSeverity() + " High"), MarkDownHelper.getLineBreak(request)); appendAll(body, MarkDownHelper.getMediumIconFromLink(request), MarkDownHelper.getNonBreakingSpace(request), MarkDownHelper.getBoldText(summary.getMediumSeverity() + " Medium"), MarkDownHelper.getLineBreak(request)); appendAll(body, MarkDownHelper.getLowIconFromLink(request), MarkDownHelper.getNonBreakingSpace(request), MarkDownHelper.getBoldText(summary.getLowSeverity() + " Low"), MarkDownHelper.getLineBreak(request)); @@ -777,7 +806,7 @@ private static void addDetailsSection(ScanRequest request, ScanResults results, if (xMap.size() > 0) { setScannerDetailsHeader(results, body); appendAll(body, "
Click to see details", CRLF, CRLF); - MarkDownHelper.appendMDtableHeaders(body,"Lines",SEVERITY,"Category","File","Link"); + MarkDownHelper.appendMDtableHeaders(body, "Lines", SEVERITY, "Category", "File", "Link"); log.info("Creating Merge/Pull Request Markdown comment"); Comparator issueComparator = Comparator