Terraform IaC Security Scanning: How to Find and Fix Misconfigurations Before They Reach Production Using tfsec, Checkov, and Trivy

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
Terraform code that provisions an S3 bucket with public access, an RDS instance without encryption, or a security group with 0.0.0.0/0 ingress on all ports will create exactly that infrastructure when applied. Security scanning in CI/CD catches these before apply runs. This guide covers how to set up tfsec and Checkov in a GitHub Actions pipeline, the top misconfiguration patterns each tool catches, and how to handle suppression for legitimate exceptions without creating a blanket ignore policy.
Installing and Running tfsec Locally
tfsec is the most widely used Terraform-specific IaC scanner. Install and run:
# Install via brew (macOS)
brew install tfsec
# Or via Docker
docker pull aquasec/tfsec
# Run against a Terraform directory
tfsec ./terraform/ --format lovely
# JSON output for CI processing
tfsec ./terraform/ --format json > tfsec-results.json
# SARIF output for GitHub Security tab
tfsec ./terraform/ --format sarif > tfsec-results.sarif
tfsec evaluates each .tf file in the target directory and reports findings with:
- Rule ID (e.g.,
aws-s3-no-public-access) - Severity (Critical, High, Medium, Low)
- File and line number
- Description and remediation guidance
- Link to documentation
For a quick first run on an existing codebase, pipe to a severity filter:
tfsec ./terraform/ --minimum-severity HIGH --format lovely
This shows only High and Critical findings, giving you the most impactful starting point without overwhelming the team with hundreds of Medium and Low findings at once.
The Most Commonly Misconfigured Terraform Resources
These resource types produce the highest-frequency high-severity findings across AWS, Azure, and GCP:
AWS S3 Buckets:
# WRONG: Public access not blocked
resource "aws_s3_bucket" "data" {
bucket = "company-data-bucket"
}
# Missing: aws_s3_bucket_public_access_block, aws_s3_bucket_server_side_encryption_configuration
# CORRECT:
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
AWS Security Groups:
# WRONG: All traffic inbound
resource "aws_security_group_rule" "allow_all" {
type = "ingress"
from_port = 0
to_port = 65535
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"] # tfsec: aws-vpc-no-public-ingress-sgr
}
Azure Storage Accounts:
# WRONG: HTTPS not required, minimum TLS not enforced
resource "azurerm_storage_account" "main" {
name = "companystorage"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
account_tier = "Standard"
account_replication_type = "GRS"
# Missing: enable_https_traffic_only, min_tls_version, network_rules
}
GCP Cloud Storage:
# WRONG: Public IAM binding
resource "google_storage_bucket_iam_member" "public" {
bucket = google_storage_bucket.data.name
role = "roles/storage.objectViewer"
member = "allUsers" # tfsec: google-storage-no-public-access
}
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
GitHub Actions Integration for PR-Level Scanning
Add scanning to every pull request that modifies .tf files:
# .github/workflows/terraform-security.yml
name: Terraform Security Scan
on:
pull_request:
paths:
- '**.tf'
- '**.tfvars'
permissions:
security-events: write # required for SARIF upload
contents: read
jobs:
tfsec:
name: tfsec
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run tfsec
uses: aquasecurity/tfsec-sarif-action@v0.1.4
with:
sarif_file: tfsec.sarif
minimum_severity: HIGH
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: tfsec.sarif
checkov:
name: Checkov
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: terraform/
framework: terraform
output_format: sarif
output_file_path: checkov.sarif
soft_fail: true # set to false to block PRs on high findings
- name: Upload Checkov SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: checkov.sarif
Set soft_fail: false once the existing finding backlog is resolved. With soft_fail: false, any High or Critical finding blocks the PR merge. Upload SARIF to the GitHub Security tab so findings are tracked as code scanning alerts alongside SAST results.
Suppressing False Positives Without Creating Security Gaps
Some findings are intentional. A public S3 bucket hosting a static website, a security group allowing 443 from the world for a load balancer, or a non-encrypted bucket for cost optimization in a staging environment. Suppression must be documented and scoped.
For tfsec, use inline ignore comments:
resource "aws_s3_bucket" "website" {
bucket = "company-public-website"
# tfsec:ignore:aws-s3-no-public-access -- Public website bucket, approved exception TICKET-4521
}
For Checkov, use checkov:skip comments:
resource "aws_security_group_rule" "alb_https" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
# checkov:skip=CKV_AWS_25: ALB HTTPS inbound -- public-facing load balancer, approved
}
Policies for suppression:
- Require a ticket reference in every suppression comment
- Review all suppressions quarterly in a PR
- Never suppress at a module level or directory level (file-level .tfsec-ignore files) without CISO approval
- Track suppression count as a metric: if it grows faster than the team's review cadence, the tool is being misused
For custom organization rules that the public scanners do not cover (e.g., required tags on all resources, specific naming conventions), tfsec supports custom rule YAML files placed in a .tfsec/ directory in the repo.
Trivy for End-to-End Terraform and Secrets Scanning
Trivy is a broader security scanner that covers IaC, container images, and secrets in a single tool. For Terraform, Trivy replicates most tfsec and Checkov checks and adds secrets detection (hardcoded API keys, passwords, AWS access keys in Terraform variable files).
# Scan Terraform directory for misconfigurations and secrets
trivy fs ./terraform/ --scanners misconfig,secret --format table
# JSON output
trivy fs ./terraform/ --scanners misconfig,secret --format json > trivy-results.json
Trivy is particularly useful for detecting hardcoded secrets in .tfvars files and in resource arguments that should reference variable inputs:
# WRONG: Hardcoded credential in Terraform resource
resource "aws_db_instance" "main" {
password = "hardcoded_password_123" # Trivy: secret detection
# Should be: password = var.db_password or a Secrets Manager reference
}
For secrets in Terraform state files (a separate problem): Terraform state can contain sensitive values in plaintext. Always use remote state with encryption at rest (S3 + SSE, Terraform Cloud, or Azure Backend with customer-managed keys). Add state file locations to your .gitignore and run Trivy on your repository to ensure no .tfstate files are committed.
The bottom line
Every terraform apply without a prior security scan is a production deployment with no code review for the security properties that matter most. Add tfsec or Checkov to your CI/CD pipeline at the PR level, upload results as SARIF to the Security tab, set a policy that High and Critical findings block merge, and require ticket references for every suppression. The scanning takes 30-60 seconds per run and prevents the kind of S3 bucket public access exposure that shows up in breach post-mortems.
Frequently asked questions
Which is better: tfsec, Checkov, or Trivy for Terraform scanning?
They overlap significantly on core rule coverage. tfsec is Terraform-specific and fast, with good HCL parsing. Checkov covers more frameworks (Terraform, Kubernetes, Dockerfiles, Ansible) and has a larger rule library including compliance framework mappings (CIS, SOC 2, PCI). Trivy adds secrets detection on top of misconfiguration scanning. Many teams run two tools in parallel: tfsec for fast PR feedback and Checkov for compliance-mapped reporting. Running all three is not unreasonable in high-compliance environments and takes under 2 minutes in CI.
Can IaC scanning detect runtime misconfigurations that are applied outside of Terraform?
No. IaC scanners analyze the Terraform code; they cannot see what exists in cloud accounts that was deployed manually, through another tool, or that drifted from the Terraform state after apply. For runtime misconfiguration detection, use a CSPM tool (Defender for Cloud, Wiz, Prisma Cloud, AWS Security Hub). IaC scanning and CSPM are complementary: IaC scanning prevents new misconfigurations from being deployed; CSPM detects existing misconfigurations regardless of how they were created.
How do I handle Terraform modules from the public registry that have findings?
When tfsec or Checkov scans a root module that calls a public registry module, findings in the module code are typically surfaced with the module source path. You cannot modify the module source, but you can: (1) pass variables to the module that enable secure configuration, (2) fork the module and apply fixes in your fork, (3) suppress the finding with a documented justification, or (4) switch to a different module with better security defaults. Check the module's GitHub issues first -- many popular modules have accepted PRs for security fixes that are not yet in a released version.
Should Terraform security scanning block the CI pipeline or just report?
Start with report-only mode (soft_fail: true) for the first 4-6 weeks to let the team understand the finding volume and resolve the existing backlog. Then switch Critical findings to blocking, then High findings. Medium and Low findings should remain advisory for most teams. The goal is to make the path of least resistance a secure one: if every PR fails due to 50 findings, engineers will configure blanket suppressions to unblock themselves, defeating the purpose.
How do I track and manage exceptions to IaC security rules at enterprise scale?
Use inline suppression comments (tfsec, Checkov, and Trivy all support suppression directives with a reason field) for one-time exceptions. Enforce a policy that every suppression must include a reason string and a Jira or issue tracker reference. In your CI pipeline, run a script that counts active suppressions per repository and outputs a suppression inventory as a build artifact. Route suppressions beyond a defined threshold for mandatory security review. Set suppression expiry by convention: if the reason references a ticket, the ticket closure should trigger suppression removal. Review the suppression inventory quarterly as part of your IaC security posture assessment.
How do I handle Terraform state file security?
Terraform state files contain plaintext values for all resource attributes, including secrets, passwords, and private keys that were passed to resources during provisioning. Never store state locally on developer machines or in version control. Use remote state backends with encryption at rest and access controls: Terraform Cloud, AWS S3 with server-side encryption and a DynamoDB lock table, Azure Blob Storage with encryption, or GCS with a customer-managed key. Restrict state file access via IAM so only the CI/CD role and designated operators can read or write state. Enable state locking to prevent concurrent applies. For particularly sensitive state (containing database credentials or certificate private keys), consider using external secret references rather than passing secrets directly to resource arguments.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
