Repository: bridgecrewio/checkov

Issue:
GitLab SAST encounters error parsing the report
**Describe the issue**
`gitlab_sast` was added to the output in #4315. GitLab SAST produced the following error when attempting to parse the report.

```
[Schema] property '/vulnerabilities/0/identifiers/0/url' is not of type: string
[Schema] property '/vulnerabilities/0/links/0/url' is not of type: string
```

I found that `url` value is set to `null` which is not supported e.g.

```json
    "vulnerabilities": [
        {
            "id": "..."
            "identifiers": [
                {
                    "name": "CKV_AWS_283",
                    "type": "checkov",
                    "url": null,
                    "value": "CKV_AWS_283"
                }
            ],
            "links": [
                {
                    "url": null
                }
            ],
           ...
  ]
```

`url` is not a required field so instead of setting the value to `null`, drop the key altogether

schema reference: https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/sast-report-format.json


Discussion:
hey @yeongzhiwei thanks for reaching out.
Didn't expect they are so picky on the values and not accepting `null` as a non-existing field. Will create a PR to fix it

Resolving patch:
diff --git a/checkov/common/output/gitlab_sast.py b/checkov/common/output/gitlab_sast.py
--- a/checkov/common/output/gitlab_sast.py
+++ b/checkov/common/output/gitlab_sast.py
@@ -79,21 +79,15 @@ def _create_vulnerabilities(self) -> list[dict[str, Any]]:
     def _create_iac_vulnerability(self, record: Record) -> dict[str, Any]:
         severity = record.severity.name.lower() if record.severity else ""
 
-        return {
+        vulnerability: "dict[str, Any]" = {
             "id": str(uuid4()),
             "identifiers": [
                 {
                     "name": record.check_id,
                     "type": "checkov",
-                    "url": record.guideline,
                     "value": record.check_id,
                 }
             ],
-            "links": [
-                {
-                    "url": record.guideline,
-                }
-            ],
             "location": {
                 "file": record.repo_file_path.lstrip("/"),
                 "start_line": record.file_line_range[0],
@@ -105,6 +99,17 @@ def _create_iac_vulnerability(self, record: Record) -> dict[str, Any]:
             "solution": f"Further info can be found {record.guideline}",
         }
 
+        if record.guideline:
+            # url can't be None
+            vulnerability["identifiers"][0]["url"] = record.guideline
+            vulnerability["links"] = [
+                {
+                    "url": record.guideline,
+                }
+            ]
+
+        return vulnerability
+
     def _create_cve_vulnerability(self, record: Record) -> dict[str, Any] | None:
         details = record.vulnerability_details
         if not details:
@@ -113,21 +118,15 @@ def _create_cve_vulnerability(self, record: Record) -> dict[str, Any] | None:
 
         severity = record.severity.name.lower() if record.severity else ""
 
-        return {
+        vulnerability: "dict[str, Any]" = {
             "id": str(uuid4()),
             "identifiers": [
                 {
                     "name": record.short_description,
                     "type": "cve",
-                    "url": details.get("link"),
                     "value": details["id"],
                 }
             ],
-            "links": [
-                {
-                    "url": details.get("link"),
-                }
-            ],
             "location": {
                 "file": record.repo_file_path.lstrip("/"),
             },
@@ -137,6 +136,18 @@ def _create_cve_vulnerability(self, record: Record) -> dict[str, Any] | None:
             "solution": details.get("status"),
         }
 
+        link = details.get("link")
+        if link:
+            # url can't be None
+            vulnerability["identifiers"][0]["url"] = link
+            vulnerability["links"] = [
+                {
+                    "url": link,
+                }
+            ]
+
+        return vulnerability
+
     def _create_license_vulnerability(self, record: Record) -> dict[str, Any] | None:
         details = record.vulnerability_details
         if not details:


Test patch:
diff --git a/tests/common/output/test_gitlab_sast_report.py b/tests/common/output/test_gitlab_sast_report.py
--- a/tests/common/output/test_gitlab_sast_report.py
+++ b/tests/common/output/test_gitlab_sast_report.py
@@ -52,12 +52,13 @@ def test_iac_output():
     # remove dynamic data
     for vul in output["vulnerabilities"]:
         del vul["id"]
-        del vul["links"]
         del vul["solution"]
         del vul["description"]
         del vul["location"]["file"]
-        for ident in vul["identifiers"]:
-            del ident["url"]
+        if "links" in vul:
+            del vul["links"]
+            for ident in vul["identifiers"]:
+                del ident["url"]
     assert sorted(output["vulnerabilities"], key=itemgetter("name")) == sorted(
         [
             {
@@ -171,7 +172,7 @@ def test_sca_package_output():
             "description": "Django before 1.11.27, 2.x before 2.2.9, and 3.x before 3.0.1 allows account takeover. ...",
             "severity": "Medium",
             "solution": "fixed in 3.0.1, 2.2.9, 1.11.27",
-        }
+        },
     ]