Security

Infrastructure Security Scanning with Checkov: How It Works and What It Checks

By Raghvendra Pandey · June 2026 · 10 min read

Most infrastructure security problems aren't novel attack vectors — they're misconfigurations that have been understood for years. S3 buckets with public ACLs. Security groups with 0.0.0.0/0 ingress on port 22. RDS instances without encryption at rest. EKS nodes running as root. These patterns appear in production infrastructure across thousands of organizations, often because the engineer who wrote the Terraform had no way of knowing the configuration was problematic.

Checkov is a static analysis tool for infrastructure-as-code. It reads your Terraform, CloudFormation, Kubernetes YAML, Dockerfile, Bicep, or Helm charts and checks them against a library of security and compliance rules before anything gets deployed. This guide explains how Checkov works, what it actually checks, how to run it effectively, and how to integrate it into a CI/CD pipeline.

How Checkov works

Checkov performs static analysis — it reads your IaC files and evaluates rules against the configuration without actually deploying anything or connecting to a cloud account. For Terraform, it reads .tf files directly (not state files or plan output) and evaluates the configuration values you've written.

Each check is a Python class or YAML specification that evaluates one or more properties of a resource. A check for S3 bucket encryption, for example, reads the server_side_encryption_configuration block of an aws_s3_bucket resource and verifies it's present and configured with AES-256 or aws:kms. If the block is absent or incorrectly configured, the check fails.

The output lists each check result against each resource:

$ checkov -d .

Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"
  FAILED for resource: aws_s3_bucket.data_lake
  File: /main.tf:1-12

Check: CKV_AWS_52: "Ensure S3 bucket has MFA delete enabled"
  FAILED for resource: aws_s3_bucket.data_lake

Check: CKV_AWS_145: "Ensure that S3 buckets are encrypted with KMS by default"
  PASSED for resource: aws_s3_bucket.data_lake

The check ID format (CKV_AWS_18) is consistent — you can reference it in skip annotations, baseline files, and CI configuration.

Security frameworks and check sources

Checkov's checks come from multiple sources:

CIS Benchmarks — The Center for Internet Security publishes hardening guides for cloud platforms. The CIS AWS Foundations Benchmark, for example, specifies requirements like multi-region CloudTrail logging, MFA on root accounts, and password policy requirements. Checkov implements many CIS controls as checks. When you see CKV_AWS_* checks, many of them correspond to specific CIS benchmark controls.

NIST frameworks — Some checks map to NIST 800-53 and NIST CSF controls, used in US federal compliance requirements.

SOC 2 — Checks relevant to Trust Services Criteria (security, availability, confidentiality). Useful for organizations pursuing SOC 2 Type II certification.

PCI DSS — Payment Card Industry Data Security Standard controls for organizations handling payment card data.

Palo Alto's research — Many checks were developed by Palo Alto Networks' Prisma Cloud team (which acquired Bridgecrew, the original Checkov creator) based on real-world incident patterns.

You can filter checks by framework:

checkov -d . --framework terraform --check-type cis_aws
checkov -d . --framework kubernetes

Running Checkov

Install and basic scan

# Install via pip
pip install checkov

# Scan a directory of Terraform files
checkov -d ./infrastructure

# Scan a single file
checkov -f main.tf

# Output JSON for programmatic processing
checkov -d . -o json > checkov-results.json

# Output SARIF format (for GitHub Code Scanning integration)
checkov -d . -o sarif > checkov-results.sarif

Targeting specific frameworks

# Terraform only
checkov -d . --framework terraform

# Kubernetes only
checkov -d . --framework kubernetes

# Multiple frameworks
checkov -d . --framework terraform --framework kubernetes

# CloudFormation
checkov -d . --framework cloudformation

Skipping specific checks

Not every check applies to every organization. You can skip checks globally or per-resource. Per-resource skips are inline annotations:

resource "aws_s3_bucket" "logs" {
  bucket = "my-logs-bucket"

  # checkov:skip=CKV_AWS_18:This bucket IS the access log destination
  # checkov:skip=CKV_AWS_52:MFA delete not required for log buckets per policy INFRA-42
}

resource "aws_security_group_rule" "bastion_ssh" {
  type        = "ingress"
  from_port   = 22
  to_port     = 22
  protocol    = "tcp"
  cidr_blocks = ["0.0.0.0/0"]

  # checkov:skip=CKV_AWS_25:Bastion host intentionally allows SSH from internet
}

The comment after the colon in the skip annotation is required — it forces the engineer to document why the skip is justified. Undocumented skips are caught by Checkov itself when you run with --skip-check-comments validation.

Global skips go on the command line:

checkov -d . --skip-check CKV_AWS_52,CKV_AWS_144

Baseline files

A baseline file records the current set of failures so that CI only fails on new findings, not pre-existing ones. This is useful for brownfield codebases:

# Generate a baseline from current failures
checkov -d . --create-baseline

# On subsequent runs, only new failures (not in baseline) fail the check
checkov -d . --baseline .checkov.baseline

Common checks and what they mean

S3

EC2 and security groups

RDS

IAM

Kubernetes

Remediating findings

Checkov output tells you what failed but not always how to fix it. The pattern for most remediations:

Missing encryption at rest (S3, RDS, EBS):

# S3 encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
  bucket = aws_s3_bucket.data.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "aws:kms"
      kms_master_key_id = aws_kms_key.s3.arn
    }
  }
}

# RDS encryption (set at creation — cannot be added to existing instance)
resource "aws_db_instance" "main" {
  storage_encrypted = true
  kms_key_id        = aws_kms_key.rds.arn
  # ...
}

Security group overly permissive:

# Before (fails CKV_AWS_25)
resource "aws_security_group_rule" "ssh" {
  cidr_blocks = ["0.0.0.0/0"]
  from_port   = 22
  to_port     = 22
  # ...
}

# After — restrict to specific CIDR or use SSM instead
resource "aws_security_group_rule" "ssh" {
  cidr_blocks = ["10.0.0.0/8"]  # internal only
  from_port   = 22
  to_port     = 22
  # ...
}

IMDSv2 enforcement:

resource "aws_instance" "app" {
  metadata_options {
    http_endpoint               = "enabled"
    http_tokens                 = "required"  # enforces IMDSv2
    http_put_response_hop_limit = 1
  }
  # ...
}

CI/CD integration

Checkov integrates into any CI system. The key decision: fail the pipeline on findings, or report them without blocking. Most teams start by reporting without blocking (to understand the current state), then enforce a zero-new-findings policy using baselines.

# GitHub Actions example
name: Security Scan
on: [pull_request]

jobs:
  checkov:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Checkov
        uses: bridgecrewio/checkov-action@master
        with:
          directory: .
          framework: terraform
          output_format: sarif
          output_file_path: checkov.sarif
          soft_fail: false  # fail the job on findings

      - name: Upload SARIF results
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: checkov.sarif

The SARIF upload sends results to GitHub Code Scanning, where findings appear as annotations on the PR diff — each finding is shown inline on the exact line of Terraform that caused it. This makes results easier to act on than reading terminal output.

When a Checkov scan returns a list of failing resources by name, it can be useful to see those resources in context — where they sit in the architecture, what connects to them. InfraSketch supports overlaying Checkov JSON results onto an architecture diagram: paste your Terraform into InfraSketch, generate the diagram, then paste the Checkov JSON output to highlight failing resources with their check counts directly on the diagram.

Custom checks

Checkov supports custom checks in Python or YAML for organization-specific policies that aren't in the built-in library:

# custom_checks/enforce_required_tags.yaml
---
metadata:
  name: Ensure required tags are present
  id: CKV_CUSTOM_1
  category: GENERAL_SECURITY

scope:
  provider: terraform

definition:
  and:
    - cond_type: attribute
      resource_types: [aws_instance, aws_rds_instance, aws_eks_cluster]
      attribute: tags.Environment
      operator: exists
    - cond_type: attribute
      resource_types: [aws_instance, aws_rds_instance, aws_eks_cluster]
      attribute: tags.Team
      operator: exists
checkov -d . --external-checks-dir ./custom_checks

YAML-based checks cover most use cases without writing Python. Python checks are needed for logic that requires evaluating multiple resources or complex conditions.

Related articles