Skip to main content
Cloud DevOps Solutions
Get a quote Talk to an engineer

Terraform / IaC

Scanning Terraform for Security Misconfigurations with Checkov and Trivy

We add static security scanning to your Terraform pull requests using Checkov, or Trivy (which absorbed tfsec). Misconfigurations such as public buckets, open security groups and unencrypted storage fail the build before apply, and exceptions are documented in code.

Key takeaways

  • Scanning infrastructure code finds misconfigurations before they exist in the cloud, when fixing them costs minutes.
  • tfsec has been folded into Trivy, so new setups choose between Checkov and Trivy.
  • Scan both the source files and the rendered plan to catch values only known at plan time.
  • A scanner that fails on everything gets disabled; we tune rules and set a baseline first.
  • Every suppressed finding carries a written reason, so exceptions are visible and reviewable.

Why scan infrastructure code at all

A cloud misconfiguration is often a one-line mistake. An S3 bucket without a public access block, a database with publicly_accessible = true, a security group open to the world on port 22. In the console these are easy to miss. In Terraform they are text, which means a machine can read them before they ever exist.

Terraform security scanning moves this check to the cheapest possible moment: the pull request. Fixing a finding there takes minutes. Fixing it after an incident takes far longer. This page describes how we add it to the pipelines we build as part of our Terraform and infrastructure as code service.

What good looks like

  • Every pull request that touches Terraform is scanned automatically.
  • Serious findings block merge; minor ones are visible but do not stop work.
  • Rules reflect your real risk, not a generic checklist.
  • Suppressions are rare, explained and attributable.
  • Scanning covers modules, not only root configurations.

Checkov, Trivy and the fate of tfsec

For years the standard choice was Checkov versus tfsec. Since then tfsec has been absorbed into Trivy, Aqua Security’s multi-purpose scanner, so its checks now run under trivy config. That leaves two mainstream open source options:

CheckovTrivy (misconfiguration)
ScopeInfrastructure as code focus, many formatsImages, dependencies, secrets and IaC in one tool
Terraform source scanYesYes
Terraform plan scanYes, via JSON planYes
Custom policiesPython or YAMLRego
Best fitTeams that want deep IaC policy coverageTeams that want one scanner everywhere

Neither is wrong. If your container pipeline already uses Trivy, extending it to Terraform avoids another tool. If you want a rich policy library and straightforward custom checks, Checkov is hard to beat. For wider policy needs, Open Policy Agent with Conftest can evaluate plan JSON against rules you write.

How it works in practice

Scanning source and plan

Source scanning is fast and needs no credentials. Plan scanning catches things only resolved at plan time, such as values from variables and data sources. We do both:

# Static scan of the directory
checkov -d . --framework terraform --compact --quiet

# Scan the rendered plan
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
checkov -f tfplan.json --framework terraform_plan

The Trivy equivalent for source is trivy config ., with the severity threshold controlling the exit code in CI.

Documented exceptions

Sometimes a finding is intentional. A public website bucket really is public. The exception belongs next to the resource, with a reason:

resource "aws_s3_bucket" "public_site" {
  bucket = "example-marketing-site"
  # checkov:skip=CKV_AWS_18: Access logging is handled at the CDN layer for this static site.
}

Because the skip lives in the code, it goes through review like anything else, and a search for checkov:skip gives you an audit list of every exception. Check the current check IDs in the version you run, since IDs and coverage evolve.

Putting it in the pipeline

The scan runs in the same job as formatting, validation and linting with tflint, so the pull request gets one pass or fail. In the Atlantis or Terraform Cloud workflow, a failed scan blocks the apply. Results can also be exported as SARIF so they appear in the code host’s security tab.

A minimal CI step

The pipeline step is short. This GitHub Actions job runs the static scan on pull requests that touch Terraform and uploads results in SARIF format so they appear in the repository’s security view:

name: terraform-scan
on:
  pull_request:
    paths: ["**.tf"]
jobs:
  checkov:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install checkov
      - run: checkov -d . --framework terraform --output sarif --output-file-path results

Pin the Checkov version in a requirements file rather than installing the latest, so a new release cannot fail every build overnight.

Decisions and trade-offs

Enforce or advise. Start in advisory mode for a couple of weeks, clean up the backlog, then enforce on high severity. Turning on blocking against a legacy repository on day one produces hundreds of failures and a revolt.

Scan modules centrally. If a module is wrong, every consumer inherits the flaw. Scanning and fixing at the module level, as described in reusable Terraform modules, fixes it once.

Scanner versus human review. Scanners are good at known bad patterns. They are poor at judging whether an IAM role is too broad for its purpose. They complement, and do not replace, review by someone who knows your estate.

When this is not enough. Static scanning does not see what is running. Runtime checks, cloud configuration monitoring and Kubernetes policy are separate layers.

Failure modes we avoid

  • Alert fatigue from default rules. We tune and baseline before enforcing.
  • Blanket skips. A file-wide disable hides real problems. Skips are per resource and per check.
  • Secrets in code. Scanners can flag hard-coded credentials, but the better fix is never to write them. Use a secrets manager and mark outputs sensitive.
  • Scanner version drift. An upgrade can add checks and suddenly fail every build. We pin versions and upgrade deliberately.

Choosing which findings block

Not every check deserves to stop a merge. We usually sort rules into three groups. Blocking rules cover exposure that is hard to reverse or easy to exploit, such as public storage, world-open admin ports and unencrypted data stores. Warning rules cover good practice with a cost, such as extra logging. Ignored rules cover checks that do not apply to your architecture, each recorded with a reason. Reviewing the grouping every quarter keeps it aligned with how your estate changes.

Delivery and handover

You receive the scanner configured in your pipeline, a documented severity policy, a cleaned-up baseline, and a short guide for developers on reading findings and requesting exceptions.

How to tell it is working

The count of high-severity findings in the main branch is zero and stays there. New findings are caught in pull requests rather than discovered in audits. The list of suppressions is short and every item has a reason. If you want a baseline scan of your current code, talk to us.

References

How we deliver it

  1. 01

    Baseline the code

    We run the scanner across existing Terraform to see current findings and sort real risks from noise.

  2. 02

    Choose and configure the tool

    Checkov or Trivy is selected, with a policy set matched to your cloud, compliance needs and risk appetite.

  3. 03

    Wire it into pull requests

    The scan runs next to fmt, validate and plan so reviewers see one combined result.

  4. 04

    Set severity gates

    High-risk findings block merge; lower ones warn and are tracked for later.

  5. 05

    Handle exceptions properly

    Intentional exceptions use inline skip comments with a reason and an owner.

  6. 06

    Add custom checks

    Where needed we write organisation-specific rules, such as required tags or approved regions.

Tools and technologies

CheckovTrivytfsecTerraformOpenTofuOpen Policy AgentConftesttflintpre-commitGitHub ActionsAtlantisSARIF

Frequently asked questions

Is tfsec still maintained?
The tfsec project was merged into Trivy by Aqua Security, and its checks now live in Trivy's misconfiguration scanning. Existing tfsec installs still run, but new setups should use trivy config or Checkov. We migrate older tfsec pipelines and map the old check IDs to current ones.
Checkov or Trivy, which is better for Terraform?
Both are solid. Checkov has a large policy library, supports custom checks in Python or YAML, and scans plan files and many other formats. Trivy is a single tool that also scans images and dependencies. We pick based on what your pipeline already uses.
What does Terraform security scanning actually catch?
Typical findings are public storage, security groups open to the internet on admin ports, unencrypted disks and databases, missing logging, overly broad IAM policies and databases without backups. It checks configuration against known good practice but cannot judge your business logic.
Will scanning slow down our developers?
A scan of a typical repository takes seconds to a minute. The bigger risk is noise: if a scanner blocks pull requests over trivial findings, people resent it. We start in warn mode, fix or suppress the backlog, and only then enforce on high severity.
Does this make us compliant with CIS or SOC 2?
No, scanning supports compliance but does not equal it. It helps enforce technical controls on infrastructure code and gives evidence of checks. Compliance also needs process, access reviews and runtime monitoring. We can map checks to frameworks as part of a wider review.

Talk to an engineer about terraform security scanning

Book a free 30-minute call. We review your current setup and tell you honestly whether and how we can help.

Written by Saddique Khan, DevOps / SRE / Cloud Consultant Engineer. Last updated 2026-10-08.