Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion checkov/common/checks/base_check_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,11 @@ def scan(
report_type=report_type or self.report_type,
file_origin_paths=[scanned_file]
):
result = self.run_check(check, entity_configuration, entity_name, entity_type, scanned_file, skip_info)
is_protected = runner_filter.protect_checks and runner_filter.check_matches(
check.id, check.bc_id, runner_filter.protect_checks
)
effective_skip_info = {} if is_protected else skip_info
result = self.run_check(check, entity_configuration, entity_name, entity_type, scanned_file, effective_skip_info)
results[check] = result
return results

Expand Down
12 changes: 12 additions & 0 deletions checkov/common/util/ext_argument_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ def write_config_file(
config_items["check"] = config_items["check"][0].split(",")
if "skip-check" in config_items.keys():
config_items["skip-check"] = config_items["skip-check"][0].split(",")
if "protect-check" in config_items.keys():
config_items["protect-check"] = config_items["protect-check"][0].split(",")
if "soft-fail-on" in config_items.keys():
config_items["soft-fail-on"] = config_items["soft-fail-on"][0].split(",")
if "hard-fail-on" in config_items.keys():
Expand Down Expand Up @@ -290,6 +292,16 @@ def add_parser_args(self) -> None:
default=None,
env_var="CKV_SKIP_CHECK",
)
self.add(
"--protect-check",
help="Checks that will always be run, even if they are skipped via --skip-check, the config file, or "
"inline skip comments (e.g. #checkov:skip=). Enter one or more items separated by commas. "
"Each item may be a Checkov check ID (CKV_AWS_123) or a BC check ID (BC_AWS_GENERAL_123). "
"This option takes precedence over all skip mechanisms.",
action="append",
default=None,
env_var="CKV_PROTECT_CHECK",
)
self.add(
"--run-all-external-checks",
action="store_true",
Expand Down
1 change: 1 addition & 0 deletions checkov/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ def run(self, banner: str = checkov_banner, tool: str = default_tool, source_typ
skip_framework=self.config.skip_framework,
checks=self.config.check,
skip_checks=self.config.skip_check,
protect_checks=self.config.protect_check,
include_all_checkov_policies=self.config.include_all_checkov_policies,
download_external_modules=convert_str_to_optional_bool(self.config.download_external_modules),
external_modules_download_path=self.config.external_modules_download_path,
Expand Down
14 changes: 12 additions & 2 deletions checkov/runner_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def __init__(
framework: Optional[List[str]] = None,
checks: Union[str, List[str], None] = None,
skip_checks: Union[str, List[str], None] = None,
protect_checks: Union[str, List[str], None] = None,
include_all_checkov_policies: bool = True,
download_external_modules: Optional[bool] = False,
external_modules_download_path: str = DEFAULT_EXTERNAL_MODULES_DIR,
Expand Down Expand Up @@ -64,6 +65,9 @@ def __init__(

checks = convert_csv_string_arg_to_list(checks)
skip_checks = convert_csv_string_arg_to_list(skip_checks)
protect_checks = convert_csv_string_arg_to_list(protect_checks)

self.protect_checks: List[str] = protect_checks or []

self.skip_invalid_secrets = skip_checks and any(skip_check.capitalize() == ValidationStatus.INVALID.value
for skip_check in skip_checks)
Expand Down Expand Up @@ -265,11 +269,17 @@ def should_run_check(
(not bc_check_id and not self.include_all_checkov_policies and not is_external and not explicit_run) or
(bc_check_id in self.suppressed_policies and bc_check_id not in self.bc_cloned_checks)
)

is_protected = self.protect_checks and self.check_matches(check_id, bc_check_id, self.protect_checks)

logging.debug(f'skip_severity = {skip_severity}, explicit_skip = {explicit_skip}, regex_match = {regex_match}, suppressed_policies: {self.suppressed_policies}')
logging.debug(
f'bc_check_id = {bc_check_id}, include_all_checkov_policies = {self.include_all_checkov_policies}, is_external = {is_external}, explicit_run: {explicit_run}')
f'bc_check_id = {bc_check_id}, include_all_checkov_policies = {self.include_all_checkov_policies}, is_external = {is_external}, explicit_run: {explicit_run}, is_protected: {is_protected}')

if should_skip_check:
if is_protected:
result = True
logging.debug(f'protect_check override {check_id}: {result}')
elif should_skip_check:
result = False
logging.debug(f'should_skip_check {check_id}: {should_skip_check}')
elif should_run_check:
Expand Down
2 changes: 2 additions & 0 deletions docs/2.Basics/CLI Command Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ nav_order: 2
| `--run-all-external-checks` | Run all external checks (loaded via `--external-checks` options) even if the checks are not present in the `--check` list. This allows you to always ensure that new checks present in the external source are used. If an external check is included in `--skip-check`, it will still be skipped. |
| `-s, --soft-fail` | Runs checks but always returns a 0 exit code. Using either `--soft-fail-on` and / or `--hard-fail-on` overrides this option, except for the case when a result does not match either of the soft fail or hard fail criteria, in which case this flag determines the result. |
| `--soft-fail-on SOFT_FAIL_ON` | Exits with a 0 exit code if only the specified items fail. Enter one or more items separated by commas. Each item may be either a Checkov check ID(CKV_AWS_123), a BC check ID (BC_AWS_GENERAL_123), or a severity (LOW, MEDIUM, HIGH, CRITICAL). If you use a severity, then any severity equal to or less than the highest severity in the list will result in a soft fail. This option may be used with `--hard-fail-on`, using the same priority logic described in `--check` and `--skip-check` options above, with `--hard-fail-on` taking precedence in a tie. If a given result does not meet the `--soft-fail-on` nor the `--hard-fail-on` criteria, then the default is to hard fail. |
| `--protect-check PROTECT_CHECK` | Checks that will always be run, even if they are present in `--skip-check`, the YAML config file, or inline skip comments (`#checkov:skip=`). Enter one or more items separated by commas. Each item may be a Checkov check ID (CKV_AWS_123) or a BC check ID (BC_AWS_GENERAL_123). This option takes precedence over all skip mechanisms and is useful for enforcing mandatory security checks that developers must not be able to suppress. Can be repeated multiple times. [env var: CKV_PROTECT_CHECK] |
| `--protect-check PROTECT_CHECK` | Checks that will always be run, even if they are present in `--skip-check`, the YAML config file, or inline skip comments (`#checkov:skip=`). Enter one or more items separated by commas. Each item may be a Checkov check ID (CKV_AWS_123) or a BC check ID (BC_AWS_GENERAL_123). This option takes precedence over all skip mechanisms and is useful for enforcing mandatory security checks that developers must not be able to suppress. Can be repeated multiple times. [env var: CKV_PROTECT_CHECK] |
| `--hard-fail-on HARD_FAIL_ON` | Exits with a non-zero exit code for specified checks. Enter one or more items separated by commas. Each item may be either a Checkov check ID (CKV_AWS_123), a BCcheck ID (BC_AWS_GENERAL_123), or a severity (LOW, MEDIUM, HIGH, CRITICAL). If you use a severity, then any severity equal to or greater than the lowest severity in the list will result in a hard fail. This option can be used with `--soft-fail-on`, using the same priority logic described in `--check` and `--skip-check` options above, with `--hard-fail-on` taking precedence in a tie. |
| `--bc-api-key BC_API_KEY` | Prisma Cloud Access Key (see `--prisma-api-url`) [env var: BC_API_KEY] |
| `--prisma-api-url PRISMA_API_URL` | The Prisma Cloud API URL (see:https://prisma.pan.dev/api/cloud/api-urls). Must be a `*.prismacloud.io`, `*.prismacloud.cn`, or `*.bridgecrew.cloud` domain. Requires `--bc-api-key` to be a Prisma Cloud Access Key in the following format: `access_key_id::secret_key` [env var: PRISMA_API_URL] |
Expand Down
51 changes: 51 additions & 0 deletions docs/2.Basics/Suppressing and Skipping Policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,57 @@ kube-system namespace:
checkov -d . --skip-check kube-system
```

# Protecting checks from being skipped

In some cases you may want to guarantee that certain checks are always executed, regardless of any skip configuration applied by developers. Use the `--protect-check` flag to mark checks as mandatory. A protected check will run even if it is present in `--skip-check`, in the YAML config file, or in an inline comment.

This is especially useful in CI/CD pipelines where a security team wants to enforce that critical checks cannot be skipped by developers.

## Usage

Via CLI:
```sh
checkov -d . --protect-check CKV_AWS_20,CKV_AWS_57
```

Via YAML config file:
```yaml
protect-check:
- CKV_AWS_20
- CKV_AWS_57
```

Via environment variable:
```sh
export CKV_PROTECT_CHECK=CKV_AWS_20,CKV_AWS_57
checkov -d .
```

## Priority order

When multiple skip mechanisms are in play, the resolution order from highest to lowest priority is:

1. `--protect-check` — always runs the check, overrides everything below
2. `--skip-check` (CLI or YAML config)
3. Inline `#checkov:skip=` comment in code

## Example

Given a resource with an inline skip comment:

```python
resource "aws_s3_bucket" "foo-bucket" {
#checkov:skip=CKV_AWS_20:The bucket is a public static content host
acl = "public-read"
}
```

Running with `--protect-check CKV_AWS_20` will **ignore** the inline comment and execute `CKV_AWS_20`, reporting a PASS or FAIL result instead of SKIPPED.

```sh
checkov -d . --protect-check CKV_AWS_20
```

# Platform enforcement rules

Checkov can download [enforcement rules](https://docs.prismacloud.io/en/enterprise-edition/content-collections/application-security/risk-management/monitor-and-manage-code-build/enforcement) that you configure in Prisma Cloud. This allows you to centralize the failure and check threshold configurations, instead of defining them in each pipeline.
Expand Down
39 changes: 39 additions & 0 deletions tests/common/test_runner_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,45 @@ def test_scan_secrets_history_limits_to_secrets_framework(self):
assert filter.enable_git_history_secret_scan is True
assert filter.framework == [CheckType.SECRETS]

# --protect-check tests

def test_protect_check_overrides_skip(self):
instance = RunnerFilter(skip_checks=["CHECK_1"], protect_checks=["CHECK_1"])
self.assertTrue(instance.should_run_check(check_id="CHECK_1"))

def test_protect_check_overrides_skip_bc_id(self):
instance = RunnerFilter(skip_checks=["BC_CHECK_1"], protect_checks=["BC_CHECK_1"])
self.assertTrue(instance.should_run_check(check_id="CHECK_1", bc_check_id="BC_CHECK_1"))

def test_protect_check_does_not_affect_other_checks(self):
instance = RunnerFilter(skip_checks=["CHECK_2"], protect_checks=["CHECK_1"])
self.assertFalse(instance.should_run_check(check_id="CHECK_2"))

def test_protect_check_does_not_force_run_non_skipped_check(self):
# protect_check on a check that is not skipped should still run normally
instance = RunnerFilter(protect_checks=["CHECK_1"])
self.assertTrue(instance.should_run_check(check_id="CHECK_1"))

def test_protect_check_wildcard_overrides_wildcard_skip(self):
# Wildcard skip of CHECK_AWS_*, but protect a specific one
instance = RunnerFilter(skip_checks=["CHECK_AWS_*"], protect_checks=["CHECK_AWS_1"])
self.assertTrue(instance.should_run_check(check_id="CHECK_AWS_1"))
self.assertFalse(instance.should_run_check(check_id="CHECK_AWS_2"))

def test_protect_check_overrides_skip_check_object(self):
from checkov.terraform.checks.resource.aws.LambdaEnvironmentCredentials import check
instance = RunnerFilter(skip_checks=[check.id], protect_checks=[check.id])
self.assertTrue(instance.should_run_check(check=check))

def test_protect_check_csv_string(self):
# protect_checks accepts a comma-separated string like CLI input
instance = RunnerFilter(skip_checks="CHECK_1,CHECK_2", protect_checks="CHECK_1,CHECK_2")
self.assertTrue(instance.should_run_check(check_id="CHECK_1"))
self.assertTrue(instance.should_run_check(check_id="CHECK_2"))

def test_protect_check_default_is_empty(self):
instance = RunnerFilter()
self.assertEqual(instance.protect_checks, [])

if __name__ == '__main__':
unittest.main()