Infrastructure-as-code changed how teams provision cloud resources. Instead of clicking through console UIs, engineers define infrastructure in version-controlled files. Terraform and Pulumi make infrastructure reproducible, reviewable, and auditable. They also make misconfigurations reproducible, deployable, and persistent at scale.
The Capital One breach (2019) was enabled in part by an overly permissive IAM role on an EC2 instance — the kind of configuration that, written in Terraform today, would look like an unremarkable 10-line resource block. The MOVEit vulnerabilities (CVE-2023-34362 and subsequent CVEs in 2023) exposed thousands of organizations partly because network exposure and default configuration decisions had propagated through standard deployment templates nobody had recently audited.
IaC security scanning isn't glamorous. It's largely about catching the same categories of misconfiguration that have appeared in breach reports for years: excessive permissions, missing encryption, public exposure of services that shouldn't be public. But catching them in CI before they deploy is substantially cheaper than finding them in a post-incident review.
The most common Terraform misconfiguration categories
Based on what IaC scanning tools consistently surface, the misconfiguration categories that appear most frequently in growing companies' Terraform codebases are:
S3 buckets with public access. AWS S3 buckets can be made public at the bucket level, the object level, or through bucket policies. The pattern that catches teams: a developer creates an S3 bucket for storing user-uploaded content and sets acl = "public-read" thinking it applies only to explicitly shared objects. In practice, it applies to everything in the bucket. Tools like Checkov rule CKV_AWS_19 and tfsec's aws-s3-no-public-buckets flag this at the Terraform level before it's deployed.
Security groups with 0.0.0.0/0 ingress. A security group rule allowing all inbound traffic from any IP address is a finding that appears in nearly every IaC scan of a real codebase. It's not always wrong — a public-facing load balancer needs to accept traffic from the internet — but it is always worth explaining. Checkov rule CKV_AWS_24 and equivalents flag these and require annotation or remediation.
Unencrypted data stores. RDS instances, ElastiCache clusters, and EBS volumes without encryption at rest are common findings. AWS makes encryption optional by default on some resource types. In Terraform: storage_encrypted = false or the absence of a kms_key_id where one is expected. These are generally straightforward fixes — adding storage_encrypted = true — but require a resource recreation that has to be planned for.
Overly permissive IAM policies. IAM policies with Action: "*" or Resource: "*" are a significant finding. The principle of least privilege requires that IAM roles and policies grant only the permissions actually needed. Reviewing IAM policy documents in Terraform is one area where automated tools are less reliable — they can flag obvious cases like wildcard actions, but evaluating whether a specific policy grants more permissions than the workload needs requires human review against your actual architecture.
Tools: Checkov versus tfsec versus Trivy
The three tools most commonly used for Terraform security scanning in CI are Checkov (Bridgecrew/Palo Alto), tfsec (now maintained as part of the Trivy ecosystem), and Trivy (Aqua Security). All three scan Terraform HCL, check against a rule library, and produce findings with severity ratings.
Trivy has become the recommendation for new setups because it covers multiple asset types in a single tool: Terraform files, Docker images, container filesystems, Kubernetes manifests, and SBOM analysis. Running one tool that covers your IaC, your container images, and your Kubernetes configs is operationally simpler than running three separate tools with three separate CI jobs.
Checkov has the largest Terraform rule library and the most active development specifically for cloud misconfigurations. If Terraform IaC is your primary concern, Checkov's rule coverage is slightly broader. Both Checkov and Trivy support custom policy files for organization-specific rules — useful when your infrastructure has legitimate exceptions to default rules.
tfsec in its original form is being merged into Trivy. If your CI currently uses tfsec, it will continue to work, but new setups should use Trivy directly.
Integrating IaC scanning into a PR gate
A GitHub Actions workflow for Trivy IaC scanning on PRs that touch Terraform files:
name: IaC Security Scan
on:
pull_request:
paths:
- 'terraform/**'
- 'infrastructure/**'
jobs:
trivy-iac:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy IaC scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'config'
scan-ref: './terraform'
severity: 'HIGH,CRITICAL'
exit-code: '1'
The paths filter means this job only runs when Terraform files change — avoiding unnecessary CI time on PRs that only modify application code. The severity: 'HIGH,CRITICAL' and exit-code: '1' combination fails the build only on high and critical findings, avoiding the noise problem of blocking on every medium-severity misconfiguration.
The limits of IaC scanning
IaC scanning catches configuration that can be statically analyzed from the Terraform files. It does not catch runtime misconfigurations — security groups modified through the AWS console after deployment, IAM policies attached manually, S3 bucket policies set outside Terraform. For these, a CSPM (Cloud Security Posture Management) tool like AWS Security Hub, Prisma Cloud, or Wiz provides continuous monitoring of deployed infrastructure state rather than pre-deployment file analysis.
We're not saying IaC scanning replaces CSPM. They're complementary layers: IaC scanning prevents new misconfigurations from being deployed; CSPM detects misconfigurations in the current deployed state including those that weren't deployed through IaC. For a team without a CSPM tool, IaC scanning in CI is the higher-priority first step — it addresses the ongoing flow of new infrastructure rather than the accumulated state.
The Terraform codebase that has never been scanned will have findings. Some will be genuinely risky; many will be acceptable trade-offs or existing exceptions. The practical approach is to run the first scan, address the critical and high findings over a sprint or two, suppress with documented justification the findings that represent intentional architecture decisions, and then run clean from that baseline forward.