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 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:
- 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.
- 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.
- Month 2: Switch High severity to blocking mode. Begin advisory mode for Medium severity.
- Month 3+: Evaluate Medium findings and permanently suppress the ones that are irrelevant. Move remaining Medium rules to blocking mode.
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.
Recommended Tool Stack by IaC Platform
| 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
- 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.
- 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.
- 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.
- 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. - 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.
