Repository: bridgecrewio/checkov

Issue:
For sarif output, add helpUri property to rules
**Describe the issue**

Currently, for sarif output, the `help` property of rules includes the URI of the full details of the rule. However, the URI should be placed in the `helpUri` property instead. See https://docs.oasis-open.org/sarif/sarif/v2.1.0/os/sarif-v2.1.0-os.html#_Toc34317848

By placing the URI in the `helpUri` property, this allows tools to use the URI to construct links, which is particularly helpful for viewing tools. For example, the Sarif Viewer (by Microsoft DevLabs) extension for VS Code appears not to make use of the `help` property, but when the `helpUri` property is supplied, it creates a hyperlink on the value of the "Rule Id" that it displays in the info panel for a rule, which makes it _extremely_ convenient to click through to see the details of the rule.


Discussion:

Resolving patch:
diff --git a/checkov/common/output/report.py b/checkov/common/output/report.py
--- a/checkov/common/output/report.py
+++ b/checkov/common/output/report.py
@@ -241,6 +241,12 @@ def get_sarif_json(self, tool: str) -> Dict[str, Any]:
         for record in self.failed_checks + self.skipped_checks:
             if self.check_type == CheckType.SCA_PACKAGE and record.check_name != SCA_PACKAGE_SCAN_CHECK_NAME:
                 continue
+
+            help_uri = record.guideline
+            if record.vulnerability_details:
+                # use the CVE link, if it is a SCA record
+                help_uri = record.vulnerability_details.get("link")
+
             rule = {
                 "id": record.check_id,
                 "name": record.check_name,
@@ -251,8 +257,9 @@ def get_sarif_json(self, tool: str) -> Dict[str, Any]:
                     "text": record.description if record.description else record.check_name,
                 },
                 "help": {
-                    "text": f'"{record.check_name}\nResource: {record.resource}\nGuideline: {record.guideline}"',
+                    "text": f'"{record.check_name}\nResource: {record.resource}"',
                 },
+                "helpUri": help_uri,
                 "defaultConfiguration": {"level": "error"},
             }
             if record.check_id not in ruleset:


Test patch:
diff --git a/tests/common/output/test_sarif_report.py b/tests/common/output/test_sarif_report.py
--- a/tests/common/output/test_sarif_report.py
+++ b/tests/common/output/test_sarif_report.py
@@ -23,6 +23,7 @@ def test_valid_passing_valid_testcases(self):
             file_abs_path=",.",
             entity_tags={"tag1": "value1"},
         )
+        record1.set_guideline("https://docs.bridgecrew.io/docs/s3_16-enable-versioning")
 
         record2 = Record(
             check_id="CKV_AWS_3",
@@ -37,17 +38,96 @@ def test_valid_passing_valid_testcases(self):
             file_abs_path=",.",
             entity_tags={"tag1": "value1"},
         )
+        record2.set_guideline("https://docs.bridgecrew.io/docs/general_7")
 
         r = Report("terraform")
         r.add_record(record=record1)
         r.add_record(record=record2)
         json_structure = r.get_sarif_json("")
-        print(json.dumps(json_structure))
+
         self.assertEqual(
             None,
             jsonschema.validate(instance=json_structure, schema=get_sarif_schema()),
         )
 
+        self.assertDictEqual(
+            json_structure,
+            {
+                "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
+                "version": "2.1.0",
+                "runs": [
+                    {
+                        "tool": {
+                            "driver": {
+                                "name": "Bridgecrew",
+                                "version": "2.1.201",
+                                "informationUri": "https://docs.bridgecrew.io",
+                                "rules": [
+                                    {
+                                        "id": "CKV_AWS_21",
+                                        "name": "Some Check",
+                                        "shortDescription": {"text": "Some Check"},
+                                        "fullDescription": {"text": "Some Check"},
+                                        "help": {"text": '"Some Check\nResource: aws_s3_bucket.operations"'},
+                                        "helpUri": "https://docs.bridgecrew.io/docs/s3_16-enable-versioning",
+                                        "defaultConfiguration": {"level": "error"},
+                                    },
+                                    {
+                                        "id": "CKV_AWS_3",
+                                        "name": "Ensure all data stored in the EBS is securely encrypted",
+                                        "shortDescription": {
+                                            "text": "Ensure all data stored in the EBS is securely encrypted"
+                                        },
+                                        "fullDescription": {
+                                            "text": "Ensure all data stored in the EBS is securely encrypted"
+                                        },
+                                        "help": {
+                                            "text": '"Ensure all data stored in the EBS is securely encrypted\nResource: aws_ebs_volume.web_host_storage"'
+                                        },
+                                        "helpUri": "https://docs.bridgecrew.io/docs/general_7",
+                                        "defaultConfiguration": {"level": "error"},
+                                    },
+                                ],
+                                "organization": "bridgecrew",
+                            }
+                        },
+                        "results": [
+                            {
+                                "ruleId": "CKV_AWS_21",
+                                "ruleIndex": 0,
+                                "level": "error",
+                                "attachments": [],
+                                "message": {"text": "Some Check"},
+                                "locations": [
+                                    {
+                                        "physicalLocation": {
+                                            "artifactLocation": {"uri": "./s3.tf"},
+                                            "region": {"startLine": 1, "endLine": 3},
+                                        }
+                                    }
+                                ],
+                            },
+                            {
+                                "ruleId": "CKV_AWS_3",
+                                "ruleIndex": 1,
+                                "level": "error",
+                                "attachments": [],
+                                "message": {"text": "Ensure all data stored in the EBS is securely encrypted"},
+                                "locations": [
+                                    {
+                                        "physicalLocation": {
+                                            "artifactLocation": {"uri": "./ec2.tf"},
+                                            "region": {"startLine": 1, "endLine": 3},
+                                        }
+                                    }
+                                ],
+                            },
+                        ],
+                    }
+                ],
+            },
+        )
+
     def test_multiple_instances_of_same_rule_do_not_break_schema(self):
         record1 = Record(
             check_id="CKV_AWS_21",
@@ -62,6 +142,7 @@ def test_multiple_instances_of_same_rule_do_not_break_schema(self):
             file_abs_path=",.",
             entity_tags={"tag1": "value1"},
         )
+        record1.set_guideline("")
 
         record2 = Record(
             check_id="CKV_AWS_111",
@@ -76,6 +157,7 @@ def test_multiple_instances_of_same_rule_do_not_break_schema(self):
             file_abs_path=",.",
             entity_tags={"tag1": "value1"},
         )
+        record2.set_guideline("")
 
         record3 = Record(
             check_id="CKV2_AWS_3",
@@ -90,6 +172,7 @@ def test_multiple_instances_of_same_rule_do_not_break_schema(self):
             file_abs_path=",.",
             entity_tags={"tag1": "value1"},
         )
+        record3.set_guideline("")
 
         record4 = Record(
             check_id="CKV2_AWS_3",
@@ -104,6 +187,7 @@ def test_multiple_instances_of_same_rule_do_not_break_schema(self):
             file_abs_path=",.",
             entity_tags={"tag1": "value1"},
         )
+        record4.set_guideline("")
 
         record5 = Record(
             check_id="CKV2_AWS_3",
@@ -118,6 +202,7 @@ def test_multiple_instances_of_same_rule_do_not_break_schema(self):
             file_abs_path=",.",
             entity_tags={"tag1": "value1"},
         )
+        record5.set_guideline("")
 
         record6 = Record(
             check_id="CKV2_AWS_3",
@@ -132,6 +217,7 @@ def test_multiple_instances_of_same_rule_do_not_break_schema(self):
             file_abs_path=",.",
             entity_tags={"tag1": "value1"},
         )
+        record6.set_guideline("")
 
         record7 = Record(
             check_id="CKV_AWS_107",
@@ -146,6 +232,7 @@ def test_multiple_instances_of_same_rule_do_not_break_schema(self):
             file_abs_path=",.",
             entity_tags={"tag1": "value1"},
         )
+        record7.set_guideline("")
 
         record8 = Record(
             check_id="CKV_AWS_110",
@@ -160,6 +247,7 @@ def test_multiple_instances_of_same_rule_do_not_break_schema(self):
             file_abs_path=",.",
             entity_tags={"tag1": "value1"},
         )
+        record8.set_guideline("")
 
         record9 = Record(
             check_id="CKV_AWS_110",
@@ -174,6 +262,7 @@ def test_multiple_instances_of_same_rule_do_not_break_schema(self):
             file_abs_path=",.",
             entity_tags={"tag1": "value1"},
         )
+        record9.set_guideline("")
 
         r = Report("terraform")
         r.add_record(record=record1)
diff --git a/tests/sca_image/test_output_reports.py b/tests/sca_image/test_output_reports.py
--- a/tests/sca_image/test_output_reports.py
+++ b/tests/sca_image/test_output_reports.py
@@ -110,7 +110,6 @@ def test_get_sarif_json(sca_image_report_scope_function):
 
     # then
     sarif_output["runs"][0]["tool"]["driver"]["version"] = "2.0.x"
-    print(sarif_output)
     assert sarif_output == \
            {
                "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
@@ -133,8 +132,9 @@ def test_get_sarif_json(sca_image_report_scope_function):
                                            "text": "SCA license"
                                        },
                                        "help": {
-                                           "text": "\"SCA license\nResource: path/to/Dockerfile (sha256:123456).perl\nGuideline: None\""
+                                           "text": "\"SCA license\nResource: path/to/Dockerfile (sha256:123456).perl\""
                                        },
+                                       "helpUri": None,
                                        "defaultConfiguration": {
                                            "level": "error"
                                        }
@@ -149,8 +149,9 @@ def test_get_sarif_json(sca_image_report_scope_function):
                                            "text": "CPAN 2.28 allows Signature Verification Bypass."
                                        },
                                        "help": {
-                                           "text": "\"SCA package scan\nResource: path/to/Dockerfile (sha256:123456).perl\nGuideline: None\""
+                                           "text": "\"SCA package scan\nResource: path/to/Dockerfile (sha256:123456).perl\""
                                        },
+                                       "helpUri": "https://people.canonical.com/~ubuntu-security/cve/2020/CVE-2020-16156",
                                        "defaultConfiguration": {
                                            "level": "error"
                                        }
@@ -165,8 +166,9 @@ def test_get_sarif_json(sca_image_report_scope_function):
                                            "text": "An out-of-bounds read vulnerability was discovered in the PCRE2 library in the get_recurse_data_length() function of the pcre2_jit_compile.c file. This issue affects recursions in JIT-compiled regular expressions caused by duplicate data transfers."
                                        },
                                        "help": {
-                                           "text": "\"SCA package scan\nResource: path/to/Dockerfile (sha256:123456).pcre2\nGuideline: None\""
+                                           "text": "\"SCA package scan\nResource: path/to/Dockerfile (sha256:123456).pcre2\""
                                        },
+                                       "helpUri": "https://people.canonical.com/~ubuntu-security/cve/2022/CVE-2022-1587",
                                        "defaultConfiguration": {
                                            "level": "error"
                                        }
@@ -181,8 +183,9 @@ def test_get_sarif_json(sca_image_report_scope_function):
                                            "text": "An out-of-bounds read vulnerability was discovered in the PCRE2 library in the compile_xclass_matchingpath() function of the pcre2_jit_compile.c file. This involves a unicode property matching issue in JIT-compiled regular expressions. The issue occurs because the character was not fully read in case-less matching within JIT."
                                        },
                                        "help": {
-                                           "text": "\"SCA package scan\nResource: path/to/Dockerfile (sha256:123456).pcre2\nGuideline: None\""
+                                           "text": "\"SCA package scan\nResource: path/to/Dockerfile (sha256:123456).pcre2\""
                                        },
+                                       "helpUri": "https://people.canonical.com/~ubuntu-security/cve/2022/CVE-2022-1586",
                                        "defaultConfiguration": {
                                            "level": "error"
                                        }
diff --git a/tests/sca_package/test_output_reports.py b/tests/sca_package/test_output_reports.py
--- a/tests/sca_package/test_output_reports.py
+++ b/tests/sca_package/test_output_reports.py
@@ -163,8 +163,9 @@ def test_get_sarif_json(sca_package_report_with_skip_scope_function):
                                     "text": "Django before 1.11.27, 2.x before 2.2.9, and 3.x before 3.0.1 allows account takeover. A suitably crafted email address (that is equal to an existing user\\'s email address after case transformation of Unicode characters) would allow an attacker to be sent a password reset token for the matched user account. (One mitigation in the new releases is to send password reset tokens only to the registered user email address.)"
                                 },
                                 "help": {
-                                    "text": '"SCA package scan\nResource: path/to/requirements.txt.django\nGuideline: None"'
+                                    "text": '"SCA package scan\nResource: path/to/requirements.txt.django"'
                                 },
+                                "helpUri": "https://nvd.nist.gov/vuln/detail/CVE-2019-19844",
                                 "defaultConfiguration": {"level": "error"},
                             },
                             {
@@ -175,8 +176,9 @@ def test_get_sarif_json(sca_package_report_with_skip_scope_function):
                                     "text": "Cross-site scripting (XSS) vulnerability in the dismissChangeRelatedObjectPopup function in contrib/admin/static/admin/js/admin/RelatedObjectLookups.js in Django before 1.8.14, 1.9.x before 1.9.8, and 1.10.x before 1.10rc1 allows remote attackers to inject arbitrary web script or HTML via vectors involving unsafe usage of Element.innerHTML."
                                 },
                                 "help": {
-                                    "text": '"SCA package scan\nResource: path/to/requirements.txt.django\nGuideline: None"'
+                                    "text": '"SCA package scan\nResource: path/to/requirements.txt.django"'
                                 },
+                                "helpUri": "https://nvd.nist.gov/vuln/detail/CVE-2016-6186",
                                 "defaultConfiguration": {"level": "error"},
                             },
                             {
@@ -187,8 +189,9 @@ def test_get_sarif_json(sca_package_report_with_skip_scope_function):
                                     "text": "The cookie parsing code in Django before 1.8.15 and 1.9.x before 1.9.10, when used on a site with Google Analytics, allows remote attackers to bypass an intended CSRF protection mechanism by setting arbitrary cookies."
                                 },
                                 "help": {
-                                    "text": '"SCA package scan\nResource: path/to/requirements.txt.django\nGuideline: None"'
+                                    "text": '"SCA package scan\nResource: path/to/requirements.txt.django"'
                                 },
+                                "helpUri": "https://nvd.nist.gov/vuln/detail/CVE-2016-7401",
                                 "defaultConfiguration": {"level": "error"},
                             },
                             {
@@ -199,8 +202,9 @@ def test_get_sarif_json(sca_package_report_with_skip_scope_function):
                                     "text": "Django before 2.2.24, 3.x before 3.1.12, and 3.2.x before 3.2.4 has a potential directory traversal via django.contrib.admindocs. Staff members could use the TemplateDetailView view to check the existence of arbitrary files. Additionally, if (and only if) the default admindocs templates have been customized by application developers to also show file contents, then not only the existence but also the file contents would have been exposed. In other words, there is directory traversal outside of the template root directories."
                                 },
                                 "help": {
-                                    "text": '"SCA package scan\nResource: path/to/requirements.txt.django\nGuideline: None"'
+                                    "text": '"SCA package scan\nResource: path/to/requirements.txt.django"'
                                 },
+                                "helpUri": "https://nvd.nist.gov/vuln/detail/CVE-2021-33203",
                                 "defaultConfiguration": {"level": "error"},
                             },
                             {
@@ -211,8 +215,9 @@ def test_get_sarif_json(sca_package_report_with_skip_scope_function):
                                     "text": "The Pallets Project Flask before 1.0 is affected by: unexpected memory usage. The impact is: denial of service. The attack vector is: crafted encoded JSON data. The fixed version is: 1. NOTE: this may overlap CVE-2018-1000656."
                                 },
                                 "help": {
-                                    "text": '"SCA package scan\nResource: path/to/requirements.txt.flask\nGuideline: None"'
+                                    "text": '"SCA package scan\nResource: path/to/requirements.txt.flask"'
                                 },
+                                "helpUri": "https://nvd.nist.gov/vuln/detail/CVE-2019-1010083",
                                 "defaultConfiguration": {"level": "error"},
                             },
                             {
@@ -223,8 +228,9 @@ def test_get_sarif_json(sca_package_report_with_skip_scope_function):
                                     "text": "The Pallets Project flask version Before 0.12.3 contains a CWE-20: Improper Input Validation vulnerability in flask that can result in Large amount of memory usage possibly leading to denial of service. This attack appear to be exploitable via Attacker provides JSON data in incorrect encoding. This vulnerability appears to have been fixed in 0.12.3. NOTE: this may overlap CVE-2019-1010083."
                                 },
                                 "help": {
-                                    "text": '"SCA package scan\nResource: path/to/requirements.txt.flask\nGuideline: None"'
+                                    "text": '"SCA package scan\nResource: path/to/requirements.txt.flask"'
                                 },
+                                "helpUri": "https://nvd.nist.gov/vuln/detail/CVE-2018-1000656",
                                 "defaultConfiguration": {"level": "error"},
                             },
                             {
@@ -235,8 +241,9 @@ def test_get_sarif_json(sca_package_report_with_skip_scope_function):
                                     "text": 'jwt-go before 4.0.0-preview1 allows attackers to bypass intended access restrictions in situations with []string{} for m[\\"aud\\"] (which is allowed by the specification). Because the type assertion fails, \\"\\" is the value of aud. This is a security problem if the JWT token is presented to a service that lacks its own audience check.'
                                 },
                                 "help": {
-                                    "text": '"SCA package scan\nResource: path/to/go.sum.github.com/dgrijalva/jwt-go\nGuideline: None"'
+                                    "text": '"SCA package scan\nResource: path/to/go.sum.github.com/dgrijalva/jwt-go"'
                                 },
+                                "helpUri": "https://nvd.nist.gov/vuln/detail/CVE-2020-26160",
                                 "defaultConfiguration": {"level": "error"},
                             },
                             {
@@ -249,8 +256,9 @@ def test_get_sarif_json(sca_package_report_with_skip_scope_function):
                                     "text": "A nil pointer dereference in the golang.org/x/crypto/ssh component through v0.0.3 for Go allows remote attackers to cause a denial of service against SSH servers."
                                 },
                                 "help": {
-                                    "text": '"SCA package scan\nResource: path/to/go.sum.golang.org/x/crypto\nGuideline: None"'
+                                    "text": '"SCA package scan\nResource: path/to/go.sum.golang.org/x/crypto"'
                                 },
+                                "helpUri": "https://nvd.nist.gov/vuln/detail/CVE-2020-29652",
                                 "defaultConfiguration": {"level": "error"},
                             },
                         ],