Repository: bridgecrewio/checkov
Pull request: Support skip comment in yaml runners
**By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.**

## Description

Supporting skipping via comment for all yaml runners

Fixes [# (2986)](https://github.com/bridgecrewio/checkov/issues/2986)


## New/Edited policies (Delete if not relevant)

### Description
* with this addition yaml runners (Gitlab CI included) will support inline comment skips


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] I have added tests that prove my feature, policy, or fix is effective and works
- [x] New and existing tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream modules


Problem statement:
Skipping via comment in .gitlab-ci not working for CKV_GITLABCI_2
**Describe the issue**

The check CKV_GITLABCI_2 is giving me a false positive. So I like to skip it as below, but this is not working. I tried different variants but only `checkov --skip-check CKV_GITLABCI_2` is actually working but globally disabling the check.

**Examples**

```yaml
planOnlySubset:
  #checkov:skip=CKV_GITLABCI_2:False positive. Does not create double pipelines.
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      when: never
    - if: $CI_PIPELINE_SOURCE != "merge_request_event" && $CI_PIPELINE_SOURCE != "schedule"
      when: manual
      allow_failure: true
```
```console
Check: CKV_GITLABCI_2: "Avoid creating rules that generate double pipelines"
        FAILED for resource: .gitlab-ci.yml.*.rules.*.rules.CKV_GITLABCI_2
        File: /.gitlab-ci.yml:43-50

                43 |   rules: # checkov:skip=CKV_GITLABCI_2
                44 |     - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
                45 |       when: never
                46 |     - if: $CI_PIPELINE_SOURCE != "merge_request_event" && $CI_PIPELINE_SOURCE != "schedule"
                47 |       when: manual
                48 |       allow_failure: true
```

**Version (please complete the following information):**
 - version: 2.0.1140

Hints from issue discussion:
We'll take two approaches.  Adding skips but also fixing the FP.  Sit tight.

Commit message: fix tests

Patch to review:
diff --git a/checkov/common/runners/object_runner.py b/checkov/common/runners/object_runner.py
index f858456d01..31c5ddd1c8 100644
--- a/checkov/common/runners/object_runner.py
+++ b/checkov/common/runners/object_runner.py
@@ -10,6 +10,7 @@
 from checkov.common.parallelizer.parallel_runner import parallel_runner
 from checkov.common.runners.base_runner import BaseRunner, filter_ignored_paths
 from checkov.runner_filter import RunnerFilter
+from checkov.common.util.suppression import collect_suppressions_for_context
 
 if TYPE_CHECKING:
     from checkov.common.checks.base_check_registry import BaseCheckRegistry
@@ -72,7 +73,8 @@ def run(
                 self._load_files(f_names, definitions, definitions_raw, lambda f: os.path.join(root, f))
 
         for file_path in definitions.keys():
-            results = registry.scan(file_path, definitions[file_path], [], runner_filter)
+            skipped_checks = collect_suppressions_for_context(definitions_raw[file_path])
+            results = registry.scan(file_path, definitions[file_path], skipped_checks, runner_filter)
             for key, result in results.items():
                 result_config = result["results_configuration"]
                 start = 0
diff --git a/checkov/common/typing.py b/checkov/common/typing.py
index 49390a20e2..01306564c9 100644
--- a/checkov/common/typing.py
+++ b/checkov/common/typing.py
@@ -27,6 +27,7 @@ class _SkippedCheck(TypedDict, total=False):
     bc_id: str | None
     id: str
     suppress_comment: str
+    line_number: int | None
 
 
 class _BaselineFinding(TypedDict):
diff --git a/checkov/common/util/suppression.py b/checkov/common/util/suppression.py
index f711a692c3..129b99b0b1 100644
--- a/checkov/common/util/suppression.py
+++ b/checkov/common/util/suppression.py
@@ -32,12 +32,13 @@ def collect_suppressions_for_context(code_lines: list[tuple[int, int | str]]) ->
 
     skipped_checks = []
     bc_id_mapping = metadata_integration.bc_to_ckv_id_mapping
-    for _, line in code_lines:
-        skip_search = re.search(COMMENT_REGEX, str(line))
+    for line_number, line_text in code_lines:
+        skip_search = re.search(COMMENT_REGEX, str(line_text))
         if skip_search:
             skipped_check: _SkippedCheck = {
                 "id": skip_search.group(2),
                 "suppress_comment": skip_search.group(3)[1:] if skip_search.group(3) else "No comment provided",
+                "line_number": line_number
             }
             # No matter which ID was used to skip, save the pair of IDs in the appropriate fields
             if bc_id_mapping and skipped_check["id"] in bc_id_mapping:
diff --git a/checkov/yaml_doc/base_registry.py b/checkov/yaml_doc/base_registry.py
index d0767cb2be..c28bdc9006 100644
--- a/checkov/yaml_doc/base_registry.py
+++ b/checkov/yaml_doc/base_registry.py
@@ -102,7 +102,7 @@ def _scan_yaml(
             results: Dict[str, Any],
     ) -> None:
         for check in checks:
-            skip_info = ([x for x in skipped_checks if x["id"] == check.id] or [{}])[0]
+            skip_info = ([x for x in skipped_checks if (x["id"] == check.id and entity["__startline__"]<= x['line_number'] and entity["__endline__"]>=x['line_number'])] or [{}])[0]
 
             if runner_filter.should_run_check(check=check):
                 scanner = self._scanner.get(check.block_type, self._scan_yaml_document)
diff --git a/tests/cloudformation/utils/test_cfn_utils.py b/tests/cloudformation/utils/test_cfn_utils.py
index 98ee5d5227..1cff176de0 100644
--- a/tests/cloudformation/utils/test_cfn_utils.py
+++ b/tests/cloudformation/utils/test_cfn_utils.py
@@ -117,6 +117,7 @@ def test_skipped_check_exists(self):
                     "id": "CKV_AWS_16",
                     "suppress_comment": "Ensure all data stored in the RDS is securely encrypted at rest",
                     "bc_id": "BC_AWS_GENERAL_4",
+                    "line_number": 30
                 },
                 {
                     "id": "CKV_AWS_17",
diff --git a/tests/generic_yaml/resources/object/skip/skip.yaml b/tests/generic_yaml/resources/object/skip/skip.yaml
new file mode 100644
index 0000000000..ac2a3b830c
--- /dev/null
+++ b/tests/generic_yaml/resources/object/skip/skip.yaml
@@ -0,0 +1,6 @@
+foo:
+  prop: value
+  # checkov:skip=CKV_FOO_1: test justification
+  otherProp: otherValue
+
+
diff --git a/tests/generic_yaml/test_runner.py b/tests/generic_yaml/test_runner.py
index 84b92b06c0..e3285d0650 100644
--- a/tests/generic_yaml/test_runner.py
+++ b/tests/generic_yaml/test_runner.py
@@ -40,6 +40,22 @@ def test_runner_object_passing_check(self):
         self.assertEqual(report.skipped_checks, [])
         report.print_console()
 
+    def test_runner_object_skip_check(self):
+        current_dir = os.path.dirname(os.path.realpath(__file__))
+        valid_dir_path = os.path.join(current_dir, "resources", "object", "skip")
+        checks_dir = os.path.join(current_dir, "checks", "object")
+        runner = Runner()
+        report = runner.run(
+            root_folder=valid_dir_path,
+            external_checks_dir=[checks_dir],
+            runner_filter=RunnerFilter(framework="all", checks=["CKV_FOO_1", "CKV_FOO_2"]),
+        )
+        self.assertEqual(len(report.passed_checks), 1)
+        self.assertEqual(report.parsing_errors, [])
+        self.assertEqual(report.failed_checks, [])
+        self.assertEqual(len(report.skipped_checks), 1)
+        report.print_console()
+
     def test_runner_array_failing_check(self):
         current_dir = os.path.dirname(os.path.realpath(__file__))
         valid_dir_path = os.path.join(current_dir, "resources", "array", "fail")


Code review comments:
diff_hunk: @@ -102,7 +102,7 @@ def _scan_yaml(
             results: Dict[str, Any],
     ) -> None:
         for check in checks:
-            skip_info = ([x for x in skipped_checks if x["id"] == check.id] or [{}])[0]
+            skip_info = ([x for x in skipped_checks if (x["id"] == check.id and entity["__startline__"]<= x['line_number'] and entity["__endline__"]>=x['line_number'])] or [{}])[0]
path: checkov/yaml_doc/base_registry.py
text: @user1:
```suggestion
            skip_info = ([x for x in skipped_checks if (x["id"] == check.id and entity["__startline__"] <= x['line_number'] <= entity["__endline__"])] or [{}])[0]
```