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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Checkov also powers [**Prisma Cloud Application Security**](https://www.prismacl
* Evaluates [Terraform Provider](https://registry.terraform.io/browse/providers) settings to regulate the creation, management, and updates of IaaS, PaaS or SaaS managed through Terraform.
* Policies support evaluation of [variables](https://github.com/bridgecrewio/checkov/blob/main/docs/2.Basics/Handling%20Variables.md) to their optional default value.
* Supports in-line [suppression](https://github.com/bridgecrewio/checkov/blob/main/docs/2.Basics/Suppressing%20and%20Skipping%20Policies.md) of accepted risks or false-positives to reduce recurring scan failures. Also supports global skip from using CLI.
* [Output](https://github.com/bridgecrewio/checkov/blob/main/docs/2.Basics/Reviewing%20Scan%20Results.md) currently available as CLI, [CycloneDX](https://cyclonedx.org), JSON, JUnit XML, CSV, SARIF and github markdown and link to remediation [guides](https://docs.prismacloud.io/en/enterprise-edition/policy-reference/).
* [Output](https://github.com/bridgecrewio/checkov/blob/main/docs/2.Basics/Reviewing%20Scan%20Results.md) currently available as CLI, [CycloneDX](https://cyclonedx.org), JSON, JUnit XML, CSV, SARIF and github markdown and link to remediation [guides](https://docs.prismacloud.io/en/enterprise-edition/policy-reference/). Policies that cannot determine pass or fail return [UNKNOWN](https://github.com/bridgecrewio/checkov/blob/main/docs/2.Basics/Unknown%20Check%20Results.md) and are included in the report and summary.

## Screenshots

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def post_runner(self, scan_report: Report) -> None:
scan_report.failed_checks = self.extend_records_with_cloned_policies(scan_report.failed_checks)
scan_report.passed_checks = self.extend_records_with_cloned_policies(scan_report.passed_checks)
scan_report.skipped_checks = self.extend_records_with_cloned_policies(scan_report.skipped_checks)
scan_report.unknown_checks = self.extend_records_with_cloned_policies(scan_report.unknown_checks)

def extend_records_with_cloned_policies(self, records: list[Record]) -> list[Record]:
bc_check_ids = [record.bc_check_id for record in records]
Expand Down
7 changes: 5 additions & 2 deletions checkov/common/bridgecrew/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def _put_json_object(s3_client: S3Client, json_obj: Any, bucket: str, object_pat

def _extract_checks_metadata(report: Report, full_repo_object_key: str, on_prem: bool) -> dict[str, dict[str, Any]]:
metadata: dict[str, dict[str, Any]] = defaultdict(dict)
for check in itertools.chain(report.passed_checks, report.failed_checks, report.skipped_checks):
for check in itertools.chain(report.passed_checks, report.failed_checks, report.skipped_checks, report.unknown_checks):
metadata_key = f'{check.file_path}:{check.resource}'
check_meta = {k: getattr(check, k, "") for k in check_metadata_keys}
check_meta['file_object_path'] = full_repo_object_key + check.file_path
Expand Down Expand Up @@ -93,7 +93,10 @@ def reduce_scan_reports(scan_reports: list[Report], on_prem: Optional[bool] = Fa
for check in report.failed_checks],
"skipped_checks": [
{k: getattr(check, k) for k in reduced_keys}
for check in report.skipped_checks]
for check in report.skipped_checks],
"unknown_checks": [
{k: getattr(check, k) for k in reduced_keys}
for check in report.unknown_checks]
},
"image_cached_results": report.image_cached_results
}
Expand Down
4 changes: 2 additions & 2 deletions checkov/common/output/csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ def __init__(self) -> None:

def add_report(self, report: Report, git_org: str, git_repository: str) -> None:
if report.check_type in (CheckType.SCA_PACKAGE, CheckType.SCA_IMAGE):
for record in itertools.chain(report.failed_checks, report.passed_checks, report.skipped_checks):
for record in itertools.chain(report.failed_checks, report.passed_checks, report.skipped_checks, report.unknown_checks):
if record.check_name == SCA_PACKAGE_SCAN_CHECK_NAME:
self.add_sca_package_resources(resource=record, git_org=git_org, git_repository=git_repository, check_type=report.check_type)
for resource in sorted(report.extra_resources):
self.add_sca_package_resources(resource=resource, git_org=git_org, git_repository=git_repository, check_type=report.check_type)
else:
for record in itertools.chain(report.failed_checks, report.passed_checks, report.skipped_checks):
for record in itertools.chain(report.failed_checks, report.passed_checks, report.skipped_checks, report.unknown_checks):
self.add_iac_resources(resource=record, git_org=git_org, git_repository=git_repository)
for resource in sorted(report.extra_resources):
self.add_iac_resources(resource=resource, git_org=git_org, git_repository=git_repository)
Expand Down
3 changes: 3 additions & 0 deletions checkov/common/output/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ def to_string(self, compact: bool = False, use_bc_ids: bool = False) -> str:
status = CheckResult.SKIPPED.name
status_color = "blue"
suppress_comment = "\tSuppress comment: {}\n".format(self.check_result.get("suppress_comment", ""))
elif self.check_result["result"] == CheckResult.UNKNOWN:
status = CheckResult.UNKNOWN.name
status_color = "yellow"

check_message = colored('Check: {}: "{}"\n'.format(self.get_output_id(use_bc_ids), self.check_name), "white")
guideline_message = self.get_guideline_string(self.guideline)
Expand Down
39 changes: 29 additions & 10 deletions checkov/common/output/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def __init__(self, check_type: str):
self.passed_checks: list[Record] = []
self.failed_checks: list[Record] = []
self.skipped_checks: list[Record] = []
self.unknown_checks: list[Record] = []
self.parsing_errors: list[str] = []
self.resources: set[str] = set()
self.extra_resources: set[ExtraResource] = set()
Expand Down Expand Up @@ -81,12 +82,15 @@ def add_record(self, record: Record) -> None:
self.failed_checks.append(record)
if record.check_result["result"] == CheckResult.SKIPPED:
self.skipped_checks.append(record)
if record.check_result["result"] == CheckResult.UNKNOWN:
self.unknown_checks.append(record)

def get_summary(self) -> Dict[str, Union[int, str]]:
return {
"passed": len(self.passed_checks),
"failed": len(self.failed_checks),
"skipped": len(self.skipped_checks),
"unknown": len(self.unknown_checks),
"parsing_errors": len(self.parsing_errors),
"resource_count": len(self.resources),
"checkov_version": version,
Expand All @@ -96,7 +100,7 @@ def get_json(self) -> str:
return json.dumps(self.get_dict(), indent=4, cls=CustomJSONEncoder)

def get_all_records(self) -> List[Record]:
return self.failed_checks + self.passed_checks + self.skipped_checks
return self.failed_checks + self.passed_checks + self.skipped_checks + self.unknown_checks

def get_dict(self, is_quiet: bool = False, url: str | None = None, full_report: bool = False, s3_setup_failed: bool = False, support_path: str | None = None) -> dict[str, Any]:
if not url and not s3_setup_failed:
Expand All @@ -118,7 +122,8 @@ def get_dict(self, is_quiet: bool = False, url: str | None = None, full_report:
"checks": {
"passed_checks": [check.__dict__ for check in self.passed_checks],
"failed_checks": [check.__dict__ for check in self.failed_checks],
"skipped_checks": [check.__dict__ for check in self.skipped_checks]
"skipped_checks": [check.__dict__ for check in self.skipped_checks],
"unknown_checks": [check.__dict__ for check in self.unknown_checks],
},
"image_cached_results": [res.__dict__ for res in self.image_cached_results]
}
Expand All @@ -129,6 +134,7 @@ def get_dict(self, is_quiet: bool = False, url: str | None = None, full_report:
"passed_checks": [check.__dict__ for check in self.passed_checks],
"failed_checks": [check.__dict__ for check in self.failed_checks],
"skipped_checks": [check.__dict__ for check in self.skipped_checks],
"unknown_checks": [check.__dict__ for check in self.unknown_checks],
"parsing_errors": list(self.parsing_errors),
},
"summary": self.get_summary(),
Expand Down Expand Up @@ -265,6 +271,7 @@ def is_empty(self, full: bool = False) -> bool:
len(self.passed_checks)
+ len(self.failed_checks)
+ len(self.skipped_checks)
+ len(self.unknown_checks)
+ len(self.parsing_errors)
)

Expand All @@ -291,17 +298,18 @@ def print_console(
summary = self.get_summary()
output_data = colored(f"{self.check_type} scan results:\n", "blue")
if self.parsing_errors:
message = "\nPassed checks: {}, Failed checks: {}, Skipped checks: {}, Parsing errors: {}\n\n".format(
message = "\nPassed checks: {}, Failed checks: {}, Skipped checks: {}, Unknown checks: {}, Parsing errors: {}\n\n".format(
summary["passed"],
summary["failed"],
summary["skipped"],
summary["unknown"],
summary["parsing_errors"],
)
else:
if self.check_type == CheckType.SCA_PACKAGE or self.check_type.lower().startswith(CheckType.SAST):
message = f"\nFailed checks: {summary['failed']}, Skipped checks: {summary['skipped']}\n\n"
else:
message = f"\nPassed checks: {summary['passed']}, Failed checks: {summary['failed']}, Skipped checks: {summary['skipped']}\n\n"
message = f"\nPassed checks: {summary['passed']}, Failed checks: {summary['failed']}, Skipped checks: {summary['skipped']}, Unknown checks: {summary['unknown']}\n\n"
if summary_position == 'top':
output_data += colored(message, "cyan")
# output for vulnerabilities is different
Expand All @@ -328,6 +336,8 @@ def print_console(
for record in self.failed_checks:
output_data += record.to_string(compact=is_compact, use_bc_ids=use_bc_ids)
if not is_quiet:
for record in self.unknown_checks:
output_data += record.to_string(compact=is_compact, use_bc_ids=use_bc_ids)
for record in self.skipped_checks:
output_data += record.to_string(compact=is_compact, use_bc_ids=use_bc_ids)

Expand Down Expand Up @@ -372,14 +382,15 @@ def print_failed_github_md(self, use_bc_ids: bool = False) -> str:
if result:
summary = self.get_summary()
if self.parsing_errors:
message = "Passed Checks: {}, Failed Checks: {}, Skipped Checks: {}, Parsing Errors: {}\n\n".format(
message = "Passed Checks: {}, Failed Checks: {}, Skipped Checks: {}, Unknown Checks: {}, Parsing Errors: {}\n\n".format(
summary["passed"],
summary["failed"],
summary["skipped"],
summary["unknown"],
summary["parsing_errors"],
)
else:
message = f"```\nPassed Checks: {summary['passed']}, Failed Checks: {summary['failed']}, Skipped Checks: {summary['skipped']}\n```\n\n"
message = f"```\nPassed Checks: {summary['passed']}, Failed Checks: {summary['failed']}, Skipped Checks: {summary['skipped']}, Unknown Checks: {summary['unknown']}\n```\n\n"

table = tabulate(
result,
Expand All @@ -396,7 +407,7 @@ def get_test_suite(self, properties: Optional[Dict[str, Any]] = None, use_bc_ids

test_cases = []

records = self.passed_checks + self.failed_checks + self.skipped_checks
records = self.passed_checks + self.failed_checks + self.skipped_checks + self.unknown_checks
for record in records:
severity = BcSeverities.NONE
if record.severity:
Expand Down Expand Up @@ -431,6 +442,8 @@ def get_test_suite(self, properties: Optional[Dict[str, Any]] = None, use_bc_ids
test_case.add_skipped_info(f"{check_id} skipped for {test_name_detail}")
else:
test_case.add_skipped_info(record.check_result.get("suppress_comment", ""))
if record.check_result["result"] == CheckResult.UNKNOWN:
test_case.add_skipped_info(f"Check result unknown: {record.check_name}")

test_cases.append(test_case)

Expand Down Expand Up @@ -615,9 +628,13 @@ def from_reduced_json(cls, json_report: dict[str, Any], check_type: str) -> Repo
report = Report(check_type)
report.image_cached_results = json_report['image_cached_results']

all_json_records = json_report["checks"]["passed_checks"] + \
json_report["checks"]["failed_checks"] + \
json_report["checks"]["skipped_checks"]
checks = json_report["checks"]
if "unknown_checks" not in checks:
checks = {**checks, "unknown_checks": []}
all_json_records = checks["passed_checks"] + \
checks["failed_checks"] + \
checks["skipped_checks"] + \
checks["unknown_checks"]

for json_record in all_json_records:
report.add_record(
Expand All @@ -631,6 +648,7 @@ def merge_reports(base_report: Report, report_to_merge: Report) -> None:
base_report.passed_checks.extend(report_to_merge.passed_checks)
base_report.failed_checks.extend(report_to_merge.failed_checks)
base_report.skipped_checks.extend(report_to_merge.skipped_checks)
base_report.unknown_checks.extend(report_to_merge.unknown_checks)
base_report.parsing_errors.extend(report_to_merge.parsing_errors)
base_report.image_cached_results.extend(report_to_merge.image_cached_results)
base_report.resources.update(report_to_merge.resources)
Expand All @@ -648,4 +666,5 @@ def dedupe_records(origin_records: list[Record]) -> list[Record]:

report.passed_checks = dedupe_records(report.passed_checks)
report.failed_checks = dedupe_records(report.failed_checks)
report.unknown_checks = dedupe_records(report.unknown_checks)
return report
8 changes: 5 additions & 3 deletions checkov/common/output/sarif.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def _create_rules(self) -> list[dict[str, Any]]:

for report in self.reports:
if report.check_type in SCA_CHECKTYPES:
for record in itertools.chain(report.failed_checks, report.skipped_checks):
for record in itertools.chain(report.failed_checks, report.skipped_checks, report.unknown_checks):
rule = None
if record.check_id.startswith("BC_LIC"):
rule = self._create_license_rule(check_type=report.check_type, record=record)
Expand All @@ -85,7 +85,7 @@ def _create_rules(self) -> list[dict[str, Any]]:
rules.append(rule)
rule_idx += 1
else:
for record in itertools.chain(report.failed_checks, report.skipped_checks):
for record in itertools.chain(report.failed_checks, report.skipped_checks, report.unknown_checks):
if record.check_id not in self.rule_index_map:
rule = self._create_iac_rule(check_type=report.check_type, record=record)
self.rule_index_map[rule["id"]] = rule_idx
Expand Down Expand Up @@ -199,12 +199,14 @@ def _create_results(self) -> list[dict[str, Any]]:
results: "list[dict[str, Any]]" = []

for report in self.reports:
for record in itertools.chain(report.failed_checks, report.skipped_checks):
for record in itertools.chain(report.failed_checks, report.skipped_checks, report.unknown_checks):
level = "warning"
if record.severity:
level = SEVERITY_TO_SARIF_LEVEL.get(record.severity.name.lower(), "none")
elif record.check_result.get("result") == CheckResult.FAILED:
level = "error"
elif record.check_result.get("result") == CheckResult.UNKNOWN:
level = "note"

rule_id = self._create_rule_id(check_type=report.check_type, record=record)
if not rule_id or rule_id not in self.rule_index_map:
Expand Down
2 changes: 1 addition & 1 deletion checkov/common/runners/runner_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ def remove_runner(self, runner: _BaseRunner) -> None:

@staticmethod
def enrich_report_with_guidelines(scan_report: Report) -> None:
for record in itertools.chain(scan_report.failed_checks, scan_report.passed_checks, scan_report.skipped_checks):
for record in itertools.chain(scan_report.failed_checks, scan_report.passed_checks, scan_report.skipped_checks, scan_report.unknown_checks):
guideline = metadata_integration.get_guideline(record.check_id)
if guideline:
record.set_guideline(guideline)
Expand Down
1 change: 1 addition & 0 deletions checkov/common/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class _ReducedScanReportCheck(TypedDict):
failed_checks: list[dict[str, Any]]
passed_checks: list[dict[str, Any]]
skipped_checks: list[dict[str, Any]]
unknown_checks: list[dict[str, Any]]


class _CicdDetails(TypedDict, total=False):
Expand Down
2 changes: 2 additions & 0 deletions docs/2.Basics/Reviewing Scan Results.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ nav_order: 6

The results of Checkov scans can be viewed in CLI, JSON, JUnit XML, SARIF, or Markdown

Some checks return **UNKNOWN** when a value cannot be determined (e.g. variable-dependent or plan `after_unknown`). These are included in the report and summary; see [Unknown Check Results](Unknown%20Check%20Results.md) for details and examples.

> Note: For Markdown output, you need to use `github_failed_only` as the `--output` type

## Scan Result Sample (CLI)
Expand Down
Loading