Cloud Security Posture18 min read0 views

Infrastructure as Code Security Scanning: Terraform and CloudFormation [2026 Guide]

Complete guide to IaC security scanning — integrate Checkov, tfsec, KICS, and Snyk into your Terraform and CloudFormation pipelines with pre-commit hooks, CI gates, and policy-as-code enforcement.

David Olowatobi

David Olowatobi

Cloud Security Architect · May 1, 2026

Infrastructure as Code Security Scanning: Terraform and CloudFormation [2026 Guide]

Key Takeaways

  • IaC security scanning catches misconfigurations before deployment — shifting from expensive runtime remediation to cheap pre-deploy prevention that blocks 85 percent of cloud security issues at the source.
  • Checkov dominates Terraform scanning with 3,000+ built-in policies, while cfn-lint and cfn_nag provide CloudFormation-specific coverage that generic scanners miss.
  • The most effective IaC security programs layer three enforcement points: pre-commit hooks for developer feedback, CI pipeline gates that block merges, and OPA/Sentinel policy-as-code for organizational guardrails.
  • Custom policies using OPA Rego or HashiCorp Sentinel let you codify organization-specific rules like naming conventions, tagging requirements, and approved instance types that built-in rules cannot cover.
  • False positive management is the difference between a scanner developers use and one they ignore — start with high-severity rules only and expand coverage as your team builds trust in the tooling.

Every cloud misconfiguration that reaches production started as a line of code that nobody reviewed for security. The S3 bucket that leaked customer data? Someone wrote acl = "public-read" in a Terraform file and merged it without a second thought. The security group that gave attackers a foothold? A CloudFormation template with 0.0.0.0/0 in the ingress rules that passed code review because nobody knew what to look for.

Infrastructure as Code security scanning eliminates this entire category of risk. Instead of deploying misconfigurations and hoping your CSPM catches them before an attacker does, you catch them the moment they are written — in the IDE, in the pull request, or at worst, in the CI pipeline before deployment.

This guide covers the complete IaC security scanning ecosystem for 2026: the best open-source and commercial scanners, how to integrate them at every stage of your development workflow, writing custom policies for organization-specific rules, and building an IaC security program that developers actually use instead of work around.

Why IaC Security Scanning Matters More Than Runtime Detection

The economics of cloud security overwhelmingly favor prevention over detection. Fixing a misconfiguration in a Terraform file takes a developer thirty seconds. Fixing the same misconfiguration after it has been deployed, detected by a CSPM, triaged by a security engineer, escalated to the responsible team, and remediated through a change management process takes days — and that is the optimistic scenario where nobody exploited it first.

The Cost Multiplier of Late Detection

Detection Stage Avg. Fix Time Fix Process Blast Radius
IDE / Pre-commit 30 seconds Developer edits locally Zero — never deployed
CI Pipeline Gate 5-15 minutes Developer fixes + re-pushes Zero — blocked at merge
CSPM Runtime Detection 2-5 days Triage → assign → PR → deploy Live — exposed until fixed
Breach / Incident 30-90 days IR → forensics → fix → review Data loss, regulatory fines
IaC Security — 4 Enforcement Points (Shift Left = Lower Cost) Low cost, zero risk High cost, data loss IDE + Pre-commit tfsec VS Code ext Checkov pre-commit Inline feedback Fix: 30 sec CI Pipeline Gate GitHub Actions step Block merge on fail PR annotations Fix: 5-15 min Policy-as-Code OPA / Sentinel Org-wide guardrails Custom rules Blocks non-compliant Runtime CSPM Wiz / Security Hub Drift detection Console changes Fix: 2-5 days
The four enforcement points for IaC security — every stage further right costs exponentially more to fix

IaC Security Scanners: Complete Comparison

The IaC scanning ecosystem has matured significantly. Here is how the major tools compare across the dimensions that matter: language support, rule coverage, integration depth, and whether they analyze static source or resolved plans.

Scanner Overview

Scanner IaC Languages Built-in Rules Custom Policies License
Checkov TF, CFN, K8s, ARM, Helm, Serverless 3,000+ Python, YAML Apache 2.0
tfsec / Trivy Terraform HCL (native) 800+ Rego, JSON MIT
KICS TF, CFN, K8s, Docker, Ansible, Pulumi 2,600+ Rego Apache 2.0
cfn-lint + cfn_nag CloudFormation only 200+ Ruby (cfn_nag) MIT
Snyk IaC TF, CFN, K8s, ARM 1,400+ OPA Rego Freemium
Sentinel Terraform (via HCP Terraform) Foundational policies Sentinel language Commercial
OPA / Conftest Any (JSON/YAML input) Community library Rego Apache 2.0

Checkov: The Comprehensive Open-Source Standard

Checkov is the most widely adopted IaC security scanner, and for good reason. Maintained by Prisma Cloud (Palo Alto Networks), it ships with over 3,000 built-in policies covering AWS, Azure, GCP, Kubernetes, and more. It scans Terraform, CloudFormation, Kubernetes manifests, Helm charts, ARM templates, and Serverless Framework configurations from a single tool.

Getting Started with Checkov

Install Checkov via pip and run it against your Terraform directory:

# Install Checkov
pip install checkov

# Scan a Terraform directory
checkov -d ./terraform/ --framework terraform

# Scan with specific check categories
checkov -d ./terraform/ --check CKV_AWS_18,CKV_AWS_19,CKV_AWS_21

# Output as JUnit XML for CI integration
checkov -d ./terraform/ -o junitxml > checkov-results.xml

# Scan a CloudFormation template
checkov -f template.yaml --framework cloudformation

Checkov in GitHub Actions

The most effective integration point is your CI pipeline. When Checkov runs as a GitHub Actions step, it blocks pull requests that introduce misconfigurations and annotates the PR with specific findings:

name: IaC Security Scan
on:
  pull_request:
    paths:
      - 'terraform/**'
      - 'cloudformation/**'

jobs:
  checkov:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Checkov
        uses: bridgecrewio/checkov-action@v12
        with:
          directory: terraform/
          framework: terraform
          soft_fail: false
          output_format: sarif
          download_external_modules: true
      - name: Upload SARIF
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results.sarif

Setting soft_fail: false ensures the pipeline fails on any finding. In practice, start with soft_fail: true during rollout to collect findings without blocking deployments, then switch to hard failure once your team has addressed the initial backlog.

Writing Custom Checkov Policies

Built-in rules cover the CIS benchmarks and vendor best practices, but every organization has custom requirements. Checkov supports custom policies in Python or YAML. Here is a YAML-based policy that ensures all S3 buckets have a specific tagging standard:

metadata:
  id: "CUSTOM_AWS_001"
  name: "Ensure S3 buckets have required cost allocation tags"
  severity: "HIGH"
  category: "CONVENTION"

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

Save this as custom_policies/s3_tagging.yaml and pass it to Checkov with --external-checks-dir custom_policies/.

tfsec / Trivy: Terraform-Native Scanning

tfsec was the original Terraform-specific security scanner, designed from the ground up to understand HCL syntax. In 2024, Aqua Security merged tfsec into Trivy as the trivy config command, but the standalone tfsec binary remains widely used.

Why tfsec for Terraform

HCL-native parsing: tfsec parses Terraform HCL directly rather than converting to an intermediate format. This means it understands Terraform-specific constructs like count, for_each, dynamic blocks, and module references natively — producing fewer false positives than tools that treat HCL as generic YAML.

VS Code integration: The tfsec VS Code extension provides real-time security feedback as you write Terraform code. Misconfigurations are underlined in the editor with explanations and remediation guidance, giving developers feedback at the moment of writing rather than after they push.

Speed: tfsec is written in Go and scans large Terraform codebases in seconds. A repository with 500 Terraform files completes scanning in under 10 seconds — fast enough for pre-commit hooks without disrupting developer workflow.

tfsec Usage

# Install via Homebrew or Go
brew install tfsec

# Basic scan
tfsec ./terraform/

# Scan with custom severity threshold
tfsec ./terraform/ --minimum-severity HIGH

# Output as SARIF for GitHub integration
tfsec ./terraform/ --format sarif > tfsec-results.sarif

# Exclude specific rules
tfsec ./terraform/ --exclude aws-s3-enable-versioning

Inline Suppressions

When you intentionally deviate from a rule, tfsec supports inline ignore comments with mandatory explanation:

resource "aws_s3_bucket" "public_website" {
  bucket = "company-marketing-site"

  #tfsec:ignore:aws-s3-no-public-access This is an intentionally public marketing website
  #tfsec:ignore:aws-s3-enable-versioning Marketing assets do not require versioning
}

The mandatory explanation text after the rule ID ensures that every suppression has documented justification that can be reviewed during security audits.

CloudFormation Security Scanning

CloudFormation-specific scanning requires different tools than Terraform because CFN templates use JSON/YAML with AWS-specific intrinsic functions (!Ref, !Sub, !GetAtt, Fn::If) that generic YAML parsers do not understand.

cfn-lint: Syntax and Security Validation

cfn-lint is AWS's official CloudFormation linter. It validates template syntax, resource property types, and catches common security issues like overly permissive IAM policies and missing encryption configuration:

# Install
pip install cfn-lint

# Validate a template
cfn-lint template.yaml

# Validate with specific rules
cfn-lint template.yaml --include-checks I

# Ignore rules for specific resources
cfn-lint template.yaml --ignore-checks E3012

cfn_nag: Deeper Security Analysis

cfn_nag goes beyond cfn-lint with security-focused rules that check for overpermissive IAM policies, open security groups, unencrypted resources, and missing logging configuration:

# Install via RubyGems
gem install cfn-nag

# Scan a template
cfn_nag_scan --input-path template.yaml

# Scan an entire directory
cfn_nag_scan --input-path cloudformation/

# Output as JSON for CI processing
cfn_nag_scan --input-path template.yaml --output-format json

CloudFormation Guard: AWS Policy-as-Code

CloudFormation Guard (cfn-guard) is AWS's own policy-as-code tool. It uses a declarative DSL to write rules that validate CloudFormation templates against organizational policies:

# Rule: All S3 buckets must have encryption enabled
let s3_buckets = Resources[ Type == 'AWS::S3::Bucket' ]

rule s3_encryption_required when %s3_buckets !empty {
  %s3_buckets.Properties.BucketEncryption exists
  %s3_buckets.Properties.BucketEncryption
    .ServerSideEncryptionConfiguration[*]
    .ServerSideEncryptionByDefault
    .SSEAlgorithm in ['aws:kms', 'AES256']
}

# Rule: Security groups must not allow unrestricted SSH
let security_groups = Resources[ Type == 'AWS::EC2::SecurityGroup' ]

rule no_open_ssh when %security_groups !empty {
  %security_groups.Properties.SecurityGroupIngress[
    FromPort == 22 or ToPort == 22
  ].CidrIp != '0.0.0.0/0'
}

Policy-as-Code: OPA Rego and HashiCorp Sentinel

Individual scanner rules catch known misconfigurations, but policy-as-code frameworks let you express organizational governance as executable code. This is how you enforce rules like "all resources must be tagged," "only approved regions are allowed," or "databases must use the company KMS key" — rules that are specific to your organization and too custom for built-in scanners.

OPA Rego for Terraform Plan Scanning

Open Policy Agent (OPA) with Conftest evaluates the JSON output of terraform plan. This approach scans the fully-resolved configuration after variable interpolation, module resolution, and data source evaluation — catching issues that static HCL scanning misses.

# Generate plan JSON
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json

# Evaluate with Conftest
conftest test tfplan.json --policy policy/

Here is a Rego policy that enforces approved instance types and required tags:

package terraform.aws

# Deny unapproved EC2 instance types
deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_instance"
  
  allowed := {"t3.micro", "t3.small", "t3.medium", "m5.large", "m5.xlarge"}
  instance_type := resource.change.after.instance_type
  not allowed[instance_type]
  
  msg := sprintf(
    "EC2 instance '%s' uses unapproved type '%s'",
    [resource.address, instance_type]
  )
}

# Deny resources without required tags
deny[msg] {
  resource := input.resource_changes[_]
  resource.change.after.tags
  required := {"Environment", "Owner", "CostCenter"}
  tag := required[_]
  not resource.change.after.tags[tag]
  
  msg := sprintf(
    "Resource '%s' missing required tag '%s'",
    [resource.address, tag]
  )
}

HashiCorp Sentinel for HCP Terraform

If you use HCP Terraform (formerly Terraform Cloud) or Terraform Enterprise, Sentinel provides native policy enforcement that runs between terraform plan and terraform apply. Sentinel policies can be Advisory (warn only), Soft Mandatory (overridable by administrators), or Hard Mandatory (cannot be bypassed).

# Sentinel policy: enforce encryption on all S3 buckets
import "tfplan/v2" as tfplan

s3_buckets = filter tfplan.resource_changes as _, rc {
  rc.type is "aws_s3_bucket" and
  rc.mode is "managed" and
  (rc.change.actions contains "create" or rc.change.actions contains "update")
}

encryption_enforced = rule {
  all s3_buckets as _, bucket {
    bucket.change.after.server_side_encryption_configuration
      is not null
  }
}

main = rule {
  encryption_enforced
}

Choosing Between OPA and Sentinel

Factor OPA / Conftest HashiCorp Sentinel
Cost Free — open source Requires HCP Terraform paid tier
IaC scope Any IaC (TF, CFN, K8s, etc.) Terraform only
Integration point CI pipeline (any) Between plan and apply (native)
Enforcement tiers Pass/fail (custom logic) Advisory / Soft / Hard mandatory
Learning curve Rego is non-trivial Sentinel has its own syntax
Best for Multi-IaC organizations, any CI HCP Terraform users needing governance

Pre-Commit Hook Integration

The absolute cheapest place to catch misconfigurations is before the code ever leaves the developer's machine. Pre-commit hooks run scanners automatically when a developer runs git commit, blocking the commit if security issues are found.

Setting Up Pre-Commit for IaC Security

The pre-commit framework supports all major IaC scanners. Here is a comprehensive configuration that runs Checkov, tfsec, and Terraform validation on every commit:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.96.1
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terraform_tfsec
        args:
          - --args=--minimum-severity HIGH
      - id: terraform_checkov
        args:
          - --args=--check CKV_AWS_18 CKV_AWS_19 CKV_AWS_21 CKV_AWS_145

  - repo: https://github.com/aws-cloudformation/cfn-lint
    rev: v1.20.0
    hooks:
      - id: cfn-lint
        files: cloudformation/.*\.(json|yaml|yml)

  - repo: https://github.com/stelligent/cfn_nag
    rev: v0.8.10
    hooks:
      - id: cfn-nag

Install the hooks with pre-commit install. From this point on, every commit to files matching the configured patterns will trigger security scanning. The developer sees findings immediately and can fix them before pushing.

Balancing Speed and Coverage in Pre-Commit

Pre-commit hooks must be fast or developers will skip them with --no-verify. Target under 10 seconds for the complete hook suite. Strategies to maintain speed:

  • Scan only changed files: Configure hooks to scan only staged files rather than the entire repository
  • Use severity thresholds: Run only HIGH and CRITICAL checks in pre-commit. Save MEDIUM and LOW for CI where speed is less critical.
  • Skip slow checks locally: Module download and plan-based scanning are too slow for pre-commit. Run those in CI only.
  • Cache scanner binaries: tfsec and Checkov binaries should be pre-installed, not downloaded on every commit.

Managing False Positives Without Losing Coverage

The single biggest reason IaC scanning programs fail is alert fatigue. When developers see hundreds of findings — many of which are false positives or irrelevant to their context — they stop looking at scan results entirely. Managing false positives is not optional; it is the difference between a scanner that improves security and one that erodes trust.

The Suppression Framework

Suppression Type When to Use Implementation Review Cadence
Inline skip Intentional deviation with justification checkov:skip=CKV_AWS_XX:reason Quarterly
Config file exclusion Rule does not apply to your environment .checkov.yaml skip-check list Semi-annually
Baseline file Legacy resources you cannot change yet checkov --create-baseline Monthly (reduce baseline)
Never suppress Public storage, open SSH, admin IAM Hard-coded in CI config Never — always enforce

The Gradual Rollout Strategy

Do not enable all scanner rules on day one. The most successful IaC security rollouts follow this progression:

  1. Week 1-2: Enable only Critical severity rules. These are the misconfiguration categories that directly lead to data exposure — public storage, unrestricted network access, missing encryption, overprivileged IAM.
  2. Week 3-4: Add High severity rules. Run in advisory mode (warn but do not block) for two weeks to measure false positive rates and give teams time to fix existing issues.
  3. Month 2: Switch High severity to blocking mode. Begin advisory mode for Medium severity.
  4. Month 3+: Evaluate Medium findings and permanently suppress the ones that are irrelevant. Move remaining Medium rules to blocking mode.
IaC Security Maturity Model — 4 Levels L1: Ad-Hoc Manual scanner runs No CI integration Findings go to email No suppression mgmt Blocks: 10-20% of issues L2: CI-Integrated Checkov in pipeline Blocks Critical + High PR annotations Inline suppressions Blocks: 60-70% of issues L3: Full Pipeline Pre-commit + CI + plan Custom policies added Exception registry Metrics + reporting Blocks: 85% of issues L4: Governance OPA / Sentinel gates Org-wide policy repo Automated baselines CSPM drift loop Blocks: 95%+ of issues Most organizations are at L1-L2. Target L3 within 90 days, L4 within 6 months.
IaC security maturity model — each level roughly doubles the percentage of misconfigurations caught before deployment

Building a Multi-Scanner Strategy

No single scanner catches everything. The most effective IaC security programs layer multiple scanners at different integration points, with each tool covering the gaps of the others.

IaC Platform Pre-Commit CI Pipeline Policy Gate
Terraform (open source) tfsec (fast, HCL-native) Checkov (comprehensive) OPA + Conftest
Terraform (HCP) tfsec (fast, HCL-native) Checkov (comprehensive) Sentinel (native integration)
CloudFormation cfn-lint (syntax + types) cfn_nag + Checkov cfn-guard
Kubernetes manifests kubeconform (schema) Checkov + KICS OPA Gatekeeper
Multi-IaC Checkov (supports all) KICS (broadest support) OPA + Conftest (universal)

Measuring IaC Security Program Effectiveness

You cannot improve what you do not measure. Track these five metrics to demonstrate the value of your IaC security program and identify areas for improvement.

The Five Key IaC Security Metrics

  1. Pre-deploy catch rate: The percentage of misconfigurations caught before deployment (by pre-commit or CI) versus those found by CSPM in production. Target: above 85 percent.
  2. Mean time to remediate (MTTR): How long it takes from scanner finding to merged fix. For pre-commit catches, this should be minutes. For CI catches, hours. For anything reaching CSPM, track days.
  3. False positive rate: The percentage of scanner findings that are suppressed as false positives or intentional deviations. Target: below 15 percent. Above 30 percent signals that your scanner is misconfigured or your rules are too broad.
  4. Developer adoption rate: The percentage of commits that run through pre-commit hooks without being skipped via --no-verify. Target: above 90 percent. Below 70 percent means the hooks are too slow or too noisy.
  5. Baseline reduction velocity: If you started with a baseline file to suppress legacy findings, track how quickly that baseline shrinks over time. Target: 20 percent reduction per quarter.

Building the Dashboard

Aggregate scanner results into a centralized dashboard that tracks these metrics over time. Most teams use one of these approaches:

  • SARIF + GitHub Security tab: Upload scanner results in SARIF format. GitHub natively displays findings in the Security tab with trend tracking across branches.
  • Prisma Cloud Console: If you use Checkov with a Prisma Cloud account, all findings flow into the Prisma Cloud console with historical trends, suppression management, and team-level reporting.
  • Custom Grafana dashboard: Parse scanner JSON output into a time-series database (InfluxDB, Prometheus) and build custom Grafana dashboards with the specific metrics your team cares about.

The goal is not zero findings — that is impossible in any actively-developed infrastructure codebase. The goal is a continuously improving trend: fewer new findings per week, faster remediation times, higher pre-deploy catch rates, and a shrinking baseline of legacy exceptions. A mature IaC security program treats scanner results the same way developers treat test results — an essential quality signal that nobody ships without.

Frequently Asked Questions

Checkov by Prisma Cloud (Palo Alto Networks) is the most comprehensive open-source option with 3,000+ built-in policies covering AWS, Azure, GCP, and Kubernetes. For teams wanting a lightweight, Terraform-specific scanner, tfsec (now part of Trivy) offers fast HCL-native scanning with excellent VS Code integration. Enterprise teams that need policy-as-code governance should evaluate HashiCorp Sentinel (for HCP Terraform) or Open Policy Agent with Conftest.

David Olowatobi

David Olowatobi

Cloud Security Architect

Network & Cloud Security

David is a network security engineer and cloud security architect with seven years of experience securing enterprise infrastructure. He holds deep expertise in AWS, Azure, and GCP security architecture, having designed and hardened cloud environments for Fortune 500 companies. His focus is on delivering practical, scalable security solutions that protect businesses without sacrificing performance.

You Might Also Like

Azure Security Center: Complete Configuration and Best Practices Guide

Azure Security Center: Complete Configuration and Best Practices Guide

Microsoft Defender for Cloud (formerly Azure Security Center) is the unified CSPM and workload protection platform for Azure, multi-cloud, and hybrid environments. This guide covers secure score optimization, Defender plan selection, regulatory compliance configuration, Sentinel integration, and the specific policies and initiatives that harden your Azure subscriptions against real-world attack paths.

David Olowatobi
David Olowatobi

April 25, 2026

0
Free Newsletter

Stay Ahead of Cyber Threats

Get weekly cybersecurity insights and practical tips. No spam, just actionable advice to keep you safe.