Releases

IaC Security in 2026: Terraform, Checkov, and Cloud Drift Detection

Every major cloud breach in the past five years shares a common thread: misconfiguration. Not zero-day exploits, not sophisticated nation-state malware — misconfiguration. An S3 bucket left public. A security group with 0.0.0.0/0 ingress on port 22. An IAM policy granting *:* to a service account that only needed read access to a single DynamoDB […]

February 16, 2026 6 min read

Every major cloud breach in the past five years shares a common thread: misconfiguration. Not zero-day exploits, not sophisticated nation-state malware — misconfiguration. An S3 bucket left public. A security group with 0.0.0.0/0 ingress on port 22. An IAM policy granting *:* to a service account that only needed read access to a single DynamoDB table. When infrastructure is defined as code, these misconfigurations become bugs in your codebase, and they can be caught, reviewed, and prevented with the same rigor you apply to application code.

Infrastructure as Code (IaC) security is the practice of analyzing Terraform configurations, Kubernetes manifests, Helm charts, CloudFormation templates, and other declarative infrastructure definitions for security risks before they are applied. It is the shift-left principle applied to cloud infrastructure: find the problem in a pull request, not in a production incident.

This guide covers the IaC security landscape in 2026 — Terraform 1.10+ hardening, Checkov 3.x policy scanning, cloud drift detection, secrets scanning, Helm chart analysis, and CI/CD integration — with practical examples you can apply to your infrastructure today.

Why IaC Security Matters

Misconfigurations Are the Number One Cloud Risk

Gartner has repeatedly projected that through 2027, 99% of cloud security failures will be the customer’s fault, primarily through misconfiguration. The 2024 Verizon Data Breach Investigations Report found that misconfiguration and related errors accounted for a growing share of breaches involving cloud assets. These are not theoretical risks.

In 2019, Capital One suffered a breach exposing 106 million customer records. The root cause was a misconfigured WAF role that had overly permissive access to S3 buckets through an SSRF vulnerability. The IAM policy attached to the WAF’s execution role allowed it to list and read any S3 bucket in the account. Had that policy been defined in Terraform and scanned by a policy engine, the overly broad s3:* permission would have been flagged before it ever reached production.

In 2023, Microsoft disclosed a breach where a misconfigured Azure storage account exposed 38TB of internal data, including private keys, passwords, and internal Teams messages. In 2024, a misconfigured Kubernetes cluster at a healthcare provider exposed patient records because the API server was publicly accessible without authentication — a finding that both Checkov and KICS would have caught from the Terraform configuration.

The pattern is the same: human error in infrastructure configuration, compounded by the speed at which teams provision cloud resources. IaC security tools exist to make these errors structurally impossible to ship.

The IaC Attack Surface

When you adopt IaC, your infrastructure definitions become source code. This creates enormous operational benefits — version control, code review, reproducibility — but it also concentrates risk. A single Terraform module used across fifty environments means a single misconfiguration propagates fifty times. A Helm chart with a permissive securityContext deploys that weakness to every cluster that uses it.

IaC repositories also become high-value targets. If an attacker gains access to your Terraform state files, they have a complete map of your infrastructure — resource IDs, IP addresses, database connection strings, and potentially secrets stored in plaintext. If they can modify your IaC pipeline, they can inject malicious infrastructure changes that deploy through your normal CI/CD process, making detection far more difficult.

This is why IaC security is not just about scanning configurations for known bad patterns. It encompasses state file protection, pipeline security, drift detection, secrets management, and policy enforcement — all of which we cover in this guide.

Terraform Security Best Practices

State File Encryption and Access Control

Terraform state files are the crown jewels of your infrastructure codebase. They contain the full mapping between your configuration and real cloud resources, including sensitive outputs and, in some cases, secrets in plaintext. Securing state is the single most important Terraform security decision you will make.

Never store state locally or in a general-purpose Git repository. Use a remote backend with encryption at rest and strict access controls:

# backend.tf -- S3 backend with encryption and locking
terraform {
  backend "s3" {
    bucket         = "myorg-terraform-state"
    key            = "production/network/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    kms_key_id     = "arn:aws:kms:us-east-1:123456789012:key/abcd-1234-efgh-5678"
    dynamodb_table = "terraform-state-lock"

    # Enforce TLS for all state operations
    skip_metadata_api_check = false
  }
}

The S3 bucket storing your state should have versioning enabled (so you can recover from corruption or accidental deletion), public access blocked at the account level, and an IAM policy that restricts access to only the CI/CD role and a break-glass admin role. The DynamoDB table provides state locking to prevent concurrent applies from corrupting the state.

For teams using OpenTofu (the open-source Terraform fork), the backend configuration is identical. OpenTofu maintains full compatibility with Terraform state backends, so your existing state security posture carries over unchanged.

Least-Privilege Provider Configuration

A common anti-pattern is configuring Terraform’s AWS provider with an IAM user that has AdministratorAccess. This violates least-privilege and means a compromised CI/CD pipeline can do anything in your AWS account. Instead, use assume-role chains with narrowly scoped permissions:

# providers.tf -- least-privilege provider with assume role
provider "aws" {
  region = "us-east-1"

  assume_role {
    role_arn     = "arn:aws:iam::123456789012:role/TerraformNetworkRole"
    session_name = "terraform-ci-network"
    external_id  = var.external_id
  }

  default_tags {
    tags = {
      ManagedBy   = "terraform"
      Environment = var.environment
      Team        = "platform"
    }
  }
}

Each Terraform workspace or layer should assume a different role scoped to only the resources it manages. Your network layer role needs EC2, VPC, and Route 53 permissions. Your application layer role needs ECS, ALB, and CloudWatch permissions. Neither should have access to the other’s resources.

Module Pinning and Supply Chain Security

Terraform modules from the public registry or Git repositories are dependencies, and they carry the same supply chain risks as npm packages or Docker base images. Always pin modules to a specific version or Git commit:

# Good -- pinned to exact version
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.16.0"
  # ...
}

# Good -- pinned to Git tag
module "custom_networking" {
  source = "git::https://github.com/myorg/tf-modules.git//networking?ref=v2.4.1"
  # ...
}

# Bad -- unpinned, uses latest
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  # No version constraint -- any publish becomes your next deploy
}

For critical infrastructure, consider vendoring modules into your repository or running a private module registry. This gives you full control over what code executes during terraform apply and provides an audit trail of module changes.

Secrets Management

Hardcoded secrets in Terraform files are a pervasive problem. Database passwords, API keys, and TLS private keys have no business existing in .tf files, terraform.tfvars, or anywhere else in your repository. Use a secrets manager and reference secrets dynamically:

# Retrieve database password from AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = "production/database/master-password"
}

resource "aws_db_instance" "main" {
  engine               = "postgres"
  engine_version       = "16.4"
  instance_class       = "db.r6g.xlarge"
  allocated_storage    = 100
  storage_encrypted    = true
  kms_key_id           = aws_kms_key.database.arn

  username = "app_admin"
  password = data.aws_secretsmanager_secret_version.db_password.secret_string

  # Never skip final snapshot in production
  skip_final_snapshot = false
  final_snapshot_identifier = "main-db-final-${formatdate("YYYY-MM-DD", timestamp())}"
}

For Kubernetes secrets referenced in Terraform-managed workloads, use the External Secrets Operator or Sealed Secrets rather than storing plaintext values in Kubernetes Secret manifests. Kubernetes operators like External Secrets Operator watch custom resources and sync secrets from external stores (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault) into Kubernetes Secrets, keeping sensitive values out of your Git repository entirely.

Checkov: Deep Dive into Policy-as-Code Scanning

🔔 Never Miss a Breaking Change

Monthly release roundup — breaking changes, security patches, and upgrade guides across your stack.

✅ You're in! Check your inbox for confirmation.

How Checkov Works

Checkov is an open-source static analysis tool for IaC, developed by Prisma Cloud (formerly Bridgecrew, now part of Palo Alto Networks). It scans Terraform, CloudFormation, Kubernetes manifests, Helm charts, Dockerfiles, ARM templates, Serverless Framework configs, and more. As of Checkov 3.x (the current major version in 2026), it includes over 2,500 built-in policies covering AWS, Azure, GCP, Kubernetes, and general security best practices.

Checkov works by parsing your IaC files into a graph representation, then evaluating each resource node against its policy library. Policies are written in Python or as simple YAML-based rules. The graph-based approach allows Checkov to evaluate relationships between resources — for example, checking that an S3 bucket referenced by a CloudFront distribution has the correct origin access policy.

Installation and basic usage are straightforward:

# Install Checkov (requires Python 3.8+)
pip install checkov

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

# Scan with specific framework
checkov -d ./k8s-manifests/ --framework kubernetes

# Scan a single file
checkov -f main.tf

# Output as JSON for CI/CD processing
checkov -d ./infrastructure/ -o json

# Scan and show only failed checks
checkov -d ./infrastructure/ --compact

A typical Checkov scan against a Terraform directory produces output like this:

Passed checks: 142, Failed checks: 8, Skipped checks: 2

Check: CKV_AWS_145: "Ensure that RDS instances have encryption enabled"
  FAILED for resource: aws_db_instance.analytics
  File: /database.tf:45-67

Check: CKV_AWS_23: "Ensure every security group and rule has a description"
  FAILED for resource: aws_security_group.web
  File: /networking.tf:12-28

Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"
  FAILED for resource: aws_s3_bucket.uploads
  File: /storage.tf:8-15

Writing Custom Policies

Built-in policies cover common scenarios, but every organization has unique security requirements. Checkov supports custom policies in both Python and YAML. YAML policies are simpler and cover most use cases:

# custom_policies/require_encryption_tags.yaml
metadata:
  id: "CUSTOM_001"
  name: "Ensure all S3 buckets have a DataClassification tag"
  severity: "HIGH"
  category: "GENERAL_SECURITY"

definition:
  cond_type: "attribute"
  resource_types:
    - "aws_s3_bucket"
  attribute: "tags.DataClassification"
  operator: "exists"

For more complex logic, Python policies give you full programmatic control:

# custom_policies/check_rds_multi_az.py
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckCategories, CheckResult


class RDSMultiAZForProduction(BaseResourceCheck):
    def __init__(self):
        name = "Ensure production RDS instances use Multi-AZ"
        id = "CUSTOM_002"
        supported_resources = ["aws_db_instance"]
        categories = [CheckCategories.BACKUP_AND_RECOVERY]
        super().__init__(name=name, id=id,
                         categories=categories,
                         supported_resources=supported_resources)

    def scan_resource_conf(self, conf):
        tags = conf.get("tags", [{}])
        if isinstance(tags, list):
            tags = tags[0]

        environment = tags.get("Environment", "")

        # Only enforce Multi-AZ for production
        if environment == "production":
            multi_az = conf.get("multi_az", [False])
            if isinstance(multi_az, list):
                multi_az = multi_az[0]
            if multi_az:
                return CheckResult.PASSED
            return CheckResult.FAILED

        return CheckResult.PASSED


check = RDSMultiAZForProduction()

Run custom policies alongside built-in ones with the --external-checks-dir flag:

checkov -d ./infrastructure/ --external-checks-dir ./custom_policies/

Kubernetes Manifest and Helm Chart Scanning

Checkov’s Kubernetes scanning evaluates manifests against security best practices that align with the CIS Kubernetes Benchmark and the NSA/CISA Kubernetes Hardening Guidance. It checks for:

  • Containers running as root (runAsNonRoot: false or missing)
  • Missing resource limits (CPU and memory), which enable denial-of-service conditions
  • Privileged containers (privileged: true)
  • Host namespace sharing (hostPID, hostIPC, hostNetwork)
  • Missing readiness and liveness probes
  • Writable root filesystems
  • Missing seccompProfile or AppArmor annotations
  • Capabilities that should be dropped (especially NET_RAW, SYS_ADMIN)

For Helm charts, Checkov renders the chart templates with default values and then scans the rendered manifests. This catches security issues that exist in the default deployment configuration. You can also provide custom values files to test different deployment scenarios:

# Scan a Helm chart directory
checkov -d ./charts/my-app/ --framework helm

# Scan with custom values
checkov -d ./charts/my-app/ --framework helm --var-file values-production.yaml

This is especially useful when you use a single Helm chart across development and production environments. The development values might disable TLS or use permissive network policies for convenience, while the production values should enforce strict security controls. Scanning both configurations ensures that security is maintained where it matters. If you are scanning container images for vulnerabilities, Checkov complements that process by catching misconfigurations in the deployment layer that image scanning cannot see.

Cloud Drift Detection

What Drift Is and Why It Matters

Cloud drift occurs when the actual state of your infrastructure diverges from the state defined in your IaC. Someone logs into the AWS console and manually changes a security group rule. An automated remediation tool adds a tag that is not in your Terraform configuration. A team member runs kubectl edit to patch a production issue and forgets to backport the change to the manifest repository.

Drift is insidious because it undermines the fundamental promise of IaC: that your code is the source of truth for your infrastructure. When drift exists, your Terraform state says one thing, your actual infrastructure does something else, and your security posture is unknown. A security group that your Terraform says allows traffic only from your VPN CIDR might actually have an additional rule allowing 0.0.0.0/0 on port 443, added manually three months ago during an incident.

Drift detection tools continuously compare your IaC-defined desired state against the actual state of your cloud resources and alert you when they diverge.

Drift Detection Tools

driftctl was an open-source drift detection tool originally created by Snyk. While driftctl itself has been deprecated (Snyk integrated its functionality into the broader Snyk IaC platform), it established the core workflow that modern tools follow: enumerate all resources in a cloud account, compare them against the Terraform state, and report resources that are unmanaged (exist in the cloud but not in state), missing (exist in state but not in the cloud), or changed (exist in both but with different configurations).

Spacelift provides drift detection as part of its Terraform management platform. It runs scheduled terraform plan operations against your stacks and alerts when the plan detects changes — meaning the actual infrastructure differs from the configuration. Spacelift can auto-remediate by triggering a terraform apply to bring infrastructure back in line, or it can open a pull request for human review.

env0 offers similar drift detection with scheduled plan operations and Slack/webhook notifications when drift is detected. It also supports OpenTofu stacks alongside Terraform.

Terraform Cloud / HCP Terraform includes built-in drift detection in its Plus tier. It runs health checks on workspaces, detects configuration drift, and can notify teams through webhooks or its native UI.

For teams that want a lightweight, self-hosted approach, a scheduled CI/CD pipeline that runs terraform plan and parses the output for changes is the simplest starting point:

# drift-check.sh -- lightweight drift detection
#!/bin/bash
set -euo pipefail

WORKSPACE="production-network"
SLACK_WEBHOOK="${SLACK_DRIFT_WEBHOOK}"

cd "/opt/terraform/${WORKSPACE}"

# Initialize without modifying state
terraform init -input=false -no-color

# Run plan and capture exit code
# Exit code 0 = no changes, 2 = changes detected
terraform plan -detailed-exitcode -input=false -no-color -out=drift.plan 2>&1 | tee plan_output.txt
EXIT_CODE=${PIPESTATUS[0]}

if [ "$EXIT_CODE" -eq 2 ]; then
  DRIFT_SUMMARY=$(grep -E "Plan:|No changes" plan_output.txt)
  curl -s -X POST "$SLACK_WEBHOOK" \
    -H 'Content-Type: application/json' \
    -d "{\"text\": \"Drift detected in ${WORKSPACE}: ${DRIFT_SUMMARY}\"}"
  exit 1
fi

echo "No drift detected in ${WORKSPACE}"

Drift Detection Workflow

An effective drift detection program has four stages:

  1. Inventory. Enumerate all resources in your cloud accounts. Tools like AWS Config, GCP Cloud Asset Inventory, or Azure Resource Graph provide a complete picture of what exists.
  2. Compare. Match each cloud resource against your Terraform state and configuration. Identify unmanaged resources (shadow IT), missing resources (orphaned state entries), and configuration drift.
  3. Classify. Not all drift is security-relevant. A changed tag is low-severity. An opened security group rule is critical. Your drift detection pipeline should classify findings by severity and route them accordingly.
  4. Remediate. For non-critical drift, open a PR to update the Terraform configuration to match the desired state (which might mean accepting the manual change or reverting it). For security-critical drift, auto-remediate immediately by running terraform apply or by invoking a cloud-native remediation (like AWS Config auto-remediation rules).

Secrets Scanning in IaC

The Exposed Credentials Problem

Secrets in IaC repositories are far more common than teams realize. A 2023 GitGuardian report found that over 10 million secret occurrences were detected in public Git commits in a single year. Terraform files are a frequent source because they often require credentials during development: AWS access keys for testing, database passwords hardcoded during prototyping, API tokens copied in for a quick integration test.

The patterns are well-known and scannable:

  • AWS access keys (AKIA[0-9A-Z]{16}) and secret keys in provider blocks or variable defaults
  • Database passwords in terraform.tfvars or as default values in variable declarations
  • Private keys (RSA, EC, or Ed25519) embedded in tls_private_key resource outputs stored in state
  • API tokens for third-party services (Datadog, PagerDuty, Slack) in provider configurations
  • Kubernetes Secrets with base64-encoded values (which is encoding, not encryption) in manifest files

Secrets Scanning Tools

Checkov includes built-in secrets scanning since version 2.x. It uses a combination of regex patterns and entropy analysis to detect credentials in IaC files:

# Run Checkov with secrets scanning enabled
checkov -d ./infrastructure/ --framework secrets

# Example output:
# Check: CKV_SECRET_2: "AWS Access Key"
#   FAILED for resource: d]1c9aa7e.tf
#   File: /providers.tf:3-3
#   Secret Hash: a]1c**************************

gitleaks is a dedicated secrets detection tool that scans Git repositories, including the full commit history. This is important because a secret that was committed and then removed in a subsequent commit still exists in the Git history:

# Install gitleaks
brew install gitleaks  # macOS
# or download binary from GitHub releases

# Scan current repository
gitleaks detect --source . -v

# Scan with custom config for IaC-specific patterns
gitleaks detect --source . --config .gitleaks.toml

trufflehog from Truffle Security is another strong option, especially for scanning IaC repositories. It supports verification — actually testing whether detected credentials are still active — which dramatically reduces false positives:

# Scan a Git repository with verification
trufflehog git file://. --only-verified

# Scan specific file types
trufflehog filesystem ./infrastructure/ --include-paths="*.tf,*.tfvars,*.yaml,*.yml"

For defense in depth, run secrets scanning at multiple layers: pre-commit hooks (catch secrets before they enter version control), CI/CD pipeline checks (catch anything the pre-commit hook missed), and periodic full-history scans (find secrets that were committed before scanning was adopted). The same layered approach applies to container security, where you protect against vulnerabilities at the image build stage, the registry stage, and the runtime stage.

Helm Chart Security Analysis

Why Helm Charts Need Special Attention

Helm charts are the dominant packaging format for Kubernetes applications, and they present unique security challenges. A chart is a template engine: the same chart can produce wildly different Kubernetes manifests depending on the values passed to it. A chart might default to non-privileged containers but accept a securityContext.privileged: true override through values. Scanning the chart templates alone is insufficient — you need to scan the rendered output for each environment.

Public Helm charts from Bitnami, Artifact Hub, and vendor registries are third-party dependencies. Like Terraform modules, they can contain insecure defaults, outdated image references, or overly permissive RBAC configurations.

Scanning Helm Charts

Checkov’s Helm scanning renders charts and evaluates the output, as described earlier. For a dedicated Helm security tool, kubeaudit and kube-linter provide focused Kubernetes manifest scanning that works with Helm-rendered output:

# Render the chart and pipe to kube-linter
helm template my-release ./charts/my-app/ -f values-production.yaml | kube-linter lint -

# Or save rendered output and scan with Checkov
helm template my-release ./charts/my-app/ -f values-production.yaml > rendered.yaml
checkov -f rendered.yaml --framework kubernetes

Key things to look for in Helm chart security audits:

  • Default image tags. Charts that default to :latest tags create unpredictable deployments and make vulnerability tracking impossible. Pin images to digest or specific version tags.
  • RBAC scope. Many charts create ClusterRole and ClusterRoleBinding resources when namespace-scoped Role and RoleBinding would suffice. This grants the application permissions across the entire cluster.
  • Service account tokens. Auto-mounted service account tokens (automountServiceAccountToken: true, which is the default) give every pod access to the Kubernetes API. Most application pods do not need this.
  • Network policies. Charts that do not include NetworkPolicy resources leave their pods accessible to every other pod in the cluster. Check whether the chart supports network policies through values and enable them.
  • Init containers and sidecar permissions. Some charts use init containers that require elevated privileges (for tasks like setting file ownership or configuring sysctl parameters). Audit these carefully, as they can be an escalation path.

CI/CD Pipeline Integration

GitHub Actions

Integrating IaC security scanning into your CI/CD pipeline is where these tools deliver the most value. A check that runs on every pull request and blocks merges on policy violations prevents misconfigurations from ever reaching your main branch. Here is a GitHub Actions workflow that runs Checkov, secrets scanning, and Terraform validation:

# .github/workflows/iac-security.yml
name: IaC Security Scan

on:
  pull_request:
    paths:
      - 'infrastructure/**'
      - 'k8s/**'
      - 'charts/**'

permissions:
  contents: read
  pull-requests: write

jobs:
  checkov:
    name: Checkov Policy Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Checkov
        uses: bridgecrewio/checkov-action@v12
        with:
          directory: infrastructure/
          framework: terraform
          output_format: cli,sarif
          output_file_path: console,results.sarif
          soft_fail: false
          skip_check: CKV_AWS_126  # Skip if you have a documented exception
          download_external_modules: true

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

  secrets-scan:
    name: Secrets Detection
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Full history for secrets scanning

      - name: Run gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  terraform-validate:
    name: Terraform Validation
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: "1.10.3"

      - name: Terraform Init
        run: terraform -chdir=infrastructure init -backend=false

      - name: Terraform Validate
        run: terraform -chdir=infrastructure validate

      - name: Terraform Format Check
        run: terraform -chdir=infrastructure fmt -check -recursive

GitLab CI

For GitLab CI, the integration follows the same pattern with GitLab-specific syntax:

# .gitlab-ci.yml
stages:
  - validate
  - security

terraform-validate:
  stage: validate
  image: hashicorp/terraform:1.10
  script:
    - cd infrastructure
    - terraform init -backend=false
    - terraform validate
    - terraform fmt -check -recursive
  rules:
    - changes:
        - infrastructure/**/*

checkov-scan:
  stage: security
  image: bridgecrew/checkov:3
  script:
    - checkov -d infrastructure/
      --framework terraform
      --output cli --output junitxml
      --output-file-path console,checkov-results.xml
      --compact
  artifacts:
    reports:
      junit: checkov-results.xml
    when: always
  rules:
    - changes:
        - infrastructure/**/*
        - k8s/**/*
        - charts/**/*

secrets-scan:
  stage: security
  image: zricethezav/gitleaks:latest
  script:
    - gitleaks detect --source . --verbose --report-format sarif --report-path gitleaks-report.sarif
  artifacts:
    paths:
      - gitleaks-report.sarif
    when: always
  allow_failure: false
  rules:
    - changes:
        - "**/*.tf"
        - "**/*.tfvars"
        - "**/*.yaml"
        - "**/*.yml"

Pre-Commit Hooks

For the fastest feedback loop, run lightweight checks as pre-commit hooks so developers catch issues before they push:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/antonbabenko/pre-commit-tf
    rev: v1.96.1
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terraform_tflint

  - repo: https://github.com/bridgecrewio/checkov
    rev: "3.2.300"
    hooks:
      - id: checkov
        args: ['--compact', '--quiet']

  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.21.2
    hooks:
      - id: gitleaks

The layered approach matters: pre-commit hooks catch the obvious issues locally, CI/CD pipeline scans provide the authoritative check, and periodic drift detection catches what slips through both.

IaC Security Tool Comparison

The landscape of IaC security tools has matured significantly. Here is how the major open-source and commercial options compare:

Feature Checkov tfsec / Trivy KICS Snyk IaC
Maintainer Palo Alto / Prisma Cloud Aqua Security Checkmarx Snyk
License Apache 2.0 Apache 2.0 Apache 2.0 Proprietary (free tier)
Terraform Yes (HCL + plan) Yes (HCL + plan) Yes (HCL) Yes (HCL + plan)
CloudFormation Yes Yes Yes Yes
Kubernetes YAML Yes Yes Yes Yes
Helm Charts Yes (renders + scans) Yes (via Trivy) Yes Yes
Dockerfiles Yes Yes Yes Yes
ARM Templates Yes Limited Yes Yes
Secrets Scanning Yes (built-in) Yes (via Trivy) Yes No (separate product)
Custom Policies Python + YAML Rego + YAML Rego Snyk Rules (limited)
Policy Count (approx) 2,500+ 1,500+ 2,000+ 800+
Graph-Based Analysis Yes No No No
SARIF Output Yes Yes Yes Yes
CI/CD Integration GitHub, GitLab, Jenkins, Azure DevOps GitHub, GitLab, Jenkins GitHub, GitLab, Jenkins, Azure DevOps GitHub, GitLab, Bitbucket, Jenkins
Best For Comprehensive coverage, multi-framework Teams already using Trivy for image scanning Organizations with Rego expertise Teams invested in the Snyk platform

tfsec has been officially merged into Trivy by Aqua Security. If you are starting fresh, use Trivy directly — it includes tfsec’s IaC scanning alongside container image scanning and SBOM generation. Checkov’s graph-based analysis is a meaningful differentiator: it can trace relationships between resources (for example, verifying that an S3 bucket policy correctly references a specific CloudFront distribution’s OAI) in ways that flat, per-resource scanners cannot.

For most teams, Checkov is the strongest starting point due to its breadth of coverage, active development, and Python-based custom policy engine. If you already use Trivy for container image scanning, adding its IaC scanning capability avoids introducing another tool. KICS is a solid choice if your organization has OPA/Rego expertise and wants to write policies in that language.

Getting Started Checklist

Here is a prioritized checklist for implementing IaC security, ordered by impact-to-effort ratio.

  1. Encrypt and lock your Terraform state. Move state to an encrypted remote backend (S3 + KMS, GCS + CMEK, or Azure Blob + Key Vault) with state locking enabled. This is a one-time setup that protects your most sensitive IaC artifact. If using OpenTofu, the configuration is identical.
  2. Run Checkov in CI/CD on every pull request. Install Checkov, add a CI job that scans your IaC directory, and configure it to fail the build on HIGH and CRITICAL findings. Use --skip-check with documented justifications for any exceptions. Start with a soft fail to establish a baseline, then switch to hard fail once you have triaged existing findings.
  3. Add secrets scanning to your pipeline. Run gitleaks or trufflehog on every pull request. Also run a one-time full-history scan to find secrets committed before scanning was in place. Rotate any exposed credentials immediately.
  4. Pin all module and provider versions. Audit every module and required_providers block in your Terraform configuration. Add explicit version constraints. Enable Dependabot or Renovate to automate version update PRs with your security scanning as a gate.
  5. Implement least-privilege provider roles. Replace any administrator-level credentials used by Terraform with assume-role configurations scoped to the minimum permissions each workspace needs. Use separate roles for plan (read-only) and apply (write) operations.
  6. Scan Kubernetes manifests and Helm charts. If you deploy to Kubernetes, add Checkov’s Kubernetes and Helm framework scanning. This catches misconfigurations in pod security contexts, RBAC, network policies, and resource limits — the same classes of issues that enable container escape attacks.
  7. Set up drift detection. Start with a weekly scheduled terraform plan that reports drift via Slack or email. Escalate to daily checks for production environments. Use Spacelift or env0 if you want auto-remediation capabilities.
  8. Write custom policies for your organization’s requirements. Standard policies cover generic best practices, but your organization likely has specific requirements: naming conventions, mandatory tags, allowed instance types, approved regions. Encode these as custom Checkov policies so they are enforced automatically.
  9. Add pre-commit hooks. Give developers the fastest possible feedback by running terraform fmt, terraform validate, and a lightweight Checkov scan before every commit. This catches formatting issues and obvious misconfigurations before they reach CI/CD.
  10. Audit third-party Helm charts before adoption. For every Helm chart you consume from a public repository, render it with your production values and run a full security scan. Check the chart’s RBAC scope, default image tags, service account configuration, and network policy support. Treat chart adoption with the same rigor you apply to adding a new library dependency.

What Comes Next

IaC security is not a one-time implementation — it is an ongoing practice that evolves with your infrastructure. As your cloud footprint grows, you will face new challenges: multi-cloud policy consistency, governance at scale across hundreds of Terraform workspaces, supply chain verification for modules and providers, and runtime policy enforcement that bridges the gap between what your IaC defines and what actually runs in production.

The tools covered in this guide — Checkov for static analysis, gitleaks and trufflehog for secrets scanning, Spacelift and env0 for drift detection — form a strong foundation. Layer them into your CI/CD pipeline, enforce them through pull request checks, and treat policy violations with the same severity you treat failing tests.

Official IaC Security References

  • Terraform state documentation – HashiCorp’s guide to state management, remote backends, and drift detection workflows.
  • Checkov on GitHub – the open-source static-analysis scanner for Terraform, CloudFormation, Kubernetes, and Dockerfile misconfigurations.
  • Terraform plan command reference – full CLI reference for terraform plan, including the -detailed-exitcode flag used in drift detection.

Related Reading

🛠️ Interactive Tool

Check Terraform providers

Open in new tab ↗

🛠️ Try These Free Tools

⚠️ K8s Manifest Deprecation Checker

Paste your Kubernetes YAML to detect deprecated APIs before upgrading.

🐳 Dockerfile Security Linter

Paste a Dockerfile for instant security and best-practice analysis.

📦 Dependency EOL Scanner

Paste your dependency file to check for end-of-life packages.

See all free tools →

🔔

Stay ahead of breaking changes

Free email alerts for EOL dates, CVEs, and major releases across your stack.

Get Alerts →