Safeguard
Cloud Security

Catching Terraform Misconfigurations Before They Ever Reach Apply

Trivy replaced tfsec in 2023 and Checkov ships thousands of policies — here's how to wire open-source Terraform scanners into CI/CD before terraform apply runs.

Safeguard Research Team
Research
6 min read

In February 2023, Aqua Security announced that tfsec — one of the most widely adopted open-source Terraform scanners, which Aqua had acquired in 2021 — would be folded into Trivy, its unified scanner, and would stop receiving new misconfiguration checks of its own. Aqua published a rule-ID migration guide mapping legacy tfsec checks to Trivy's AVD (Aqua Vulnerability Database) identifiers, and trivy config <path> is now the maintained command for scanning Terraform source. That consolidation matters because it changed which command teams should actually be running in CI, and a surprising number of pipelines still invoke an unmaintained tfsec binary out of habit. This piece is a hands-on guide to the current landscape — Checkov, Trivy, and Terrascan — and how to wire one (or more) of them into a pipeline stage that runs against .tf source or a terraform plan JSON output, gates on severity, and blocks terraform apply before a misconfigured S3 bucket, an overly permissive IAM policy, or a security group open to 0.0.0.0/0 ever reaches a live cloud account. We'll also cover where policy-as-code with OPA/Conftest fills gaps the built-in rule sets don't cover, and how to keep results visible in a pull request instead of buried in a build log.

Which open-source scanner should you actually run?

There are three actively maintained open-source options, and they overlap more than they compete. Checkov, originally built by Bridgecrew and now maintained under Palo Alto Networks' Prisma Cloud umbrella, is Python-based and scans Terraform, CloudFormation, Kubernetes manifests, and ARM templates with a large built-in policy set plus custom checks written in Python or YAML. Trivy, Aqua Security's unified scanner, absorbed tfsec's Terraform-specific logic in 2023 and now handles container images, filesystems, SBOMs, and IaC misconfigurations through one trivy config command. Terrascan, originally Accurics and now maintained by Tenable, uses Open Policy Agent's Rego language as its policy engine and also covers Kubernetes and Helm. None of the three requires cloud credentials or a live plan to run a baseline scan — all three parse .tf source directly, which is precisely what makes them cheap to run on every commit.

Where in the pipeline should the scan actually run?

The scan should run twice: once against raw .tf source on every pull request, and again against the rendered terraform plan JSON immediately before apply. Source-level scanning with Checkov or trivy config catches structural misconfigurations — an unencrypted EBS volume, a public S3 ACL, a security group ingress rule with no CIDR restriction — without needing provider credentials, so it can run on a fork's PR before any secrets are exposed to it. Plan-level scanning, by contrast, catches issues that only exist once variables are resolved and modules are expanded — a variable default that looks safe in source but resolves to a wide-open value in a specific environment. Tools like Conftest evaluate terraform show -json plan.out against custom Rego policies for exactly this reason. The well-established pattern is: fast source scan gates the PR merge, plan-based scan gates the apply step in the deploy job, and both emit SARIF so results land as inline annotations in GitHub's code scanning UI rather than a wall of console text.

How do severity thresholds keep the pipeline from being noisy?

Every one of these scanners ships hundreds to thousands of default checks, and running them unfiltered on day one produces a PR comment nobody reads. Checkov's default policy set spans well over a thousand checks across AWS, Azure, GCP, and Kubernetes providers; Trivy's AVD similarly enumerates checks by severity (critical, high, medium, low, unknown) and by provider. The practical fix is the same one SAST and SCA teams already use: gate the CI job's exit code on a severity floor — for example, fail the build only on high or critical — while still surfacing lower-severity findings as non-blocking annotations. Checkov's --check and --skip-check flags, and Trivy's --severity flag, let a team start there and tighten the floor over successive quarters as false-positive rates drop and the team builds a baseline suppression file for accepted-risk findings rather than re-litigating them on every PR.

What can policy-as-code catch that built-in rules can't?

Built-in rule sets encode general cloud security best practice, but they can't encode your organization's own naming conventions, tagging requirements, or account-specific guardrails — that's what OPA and Conftest are for. A Rego policy can assert that every resource carries a cost-center tag, that no aws_db_instance is ever created outside an approved list of regions, or that an S3 bucket module can only be instantiated through your organization's vetted wrapper module rather than the raw provider resource. This is meaningfully different from a Checkov custom check in scope: Rego policies evaluate the full JSON plan graph, so a policy can reason about relationships between resources — for instance, blocking an internet gateway route only when it's attached to a subnet tagged private — not just a single resource's fields in isolation. Terrascan's engine is built on the same Rego foundation, so teams already invested in Terrascan can write these organization-specific policies without adopting a second language.

How does IaC scanning fit into a broader findings program, not just a Terraform gate?

Terraform misconfigurations are one input into a larger risk picture that also includes the SCA, secrets, and container findings sitting in the same repository. Safeguard's unified Application Security Testing model treats infrastructure-as-code as one finding type alongside SAST, DAST, SCA, secrets, and container scanning, so an IaC misconfiguration in a Terraform module can be viewed and triaged in the same findings queue as a vulnerable dependency or an exposed API key, rather than in a separate tfsec or Checkov dashboard nobody checks. Safeguard's platform documentation is explicit that IaC detection depth is expanding over time rather than claiming parity with dedicated engines like Checkov or Trivy on day one — which is exactly why running an open-source scanner directly in your pipeline, as described above, remains the right baseline today. Safeguard also supports Terraform Cloud and Spacelift as first-class CI/CD connectors, and ships a Terraform provider so integrations themselves can be declared as code, which keeps the scanning pipeline configuration under the same version control and review process as the infrastructure it's protecting.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.