October 20, 2025josh-pollara

Terraform Security Scanning in CI | tfsec vs Checkov for GitHub Actions

What you'll learn: Checkov vs. tfsec at a practical level, including when to use one or both. How to set up GitHub Actions to run both tools on every push and pull request, and interpret output and fail builds on critical issues so you can customize rules for your environment.

The fastest way to prevent a bad Terraform change from reaching production is to block it in Continuous Integration (CI) before it's merged. Static analysis tools like tfsec and Checkov can spot high-risk misconfigurations – open security groups, public S3 buckets, disabled encryption – long before the apply command.

Many teams also scan the Terraform plan for deeper, resolved context, which you can automate directly in Terrateam.

In this post, you'll learn about practical uses for Checkov and tfsec and how you can use Terrateam to orchestrate GitOps workflows, including scanning Terraform plans with Checkov.

If you're also using OpenTofu, we've got you covered – Checkov and tfsec fit into the same pattern.

Checkov vs. tfsec: What they're used for (and why you should use both)

Both tools enable you to analyze infrastructure-as-code (IaC) for insecure defaults and policy violations. They're optimized for speed in CI and work well as "shift-left" checks that run before provisioning.

At a glance:

CapabilitytfsecCheckov
Primary inputTerraform/HCL directoryTerraform/HCL directory and Terraform plan files
Typical CI usageFast static checks on changed codeBroader policy coverage; can scan plans generated by your CI
Output formatsText, JSON, SARIF (GitHub code scanning)Text, JSON, SARIF (GitHub code scanning)
Rule customizationAllow/ignore by code comment or config; custom checks possibleRich policy set; can extend and enforce org standards; strong plan-level context
Best forQuick, developer-friendly feedbackComprehensive policy guardrails; plan-aware checks

Why use both? A typical pattern is to run tfsec first for near-instant feedback on code diffs, then run Checkov after terraform plan to evaluate the complete change set with resource values resolved. Checkov's Terraform Plan Scanning has official documentation, and Terrateam can run that step automatically against the generated plan.

Checkov supports SARIF output and GitHub code scanning workflows, while tfsec supports SARIF output for GitHub code scanning.

A GitHub Actions configuration to run tfsec and Checkov on every PR

Below is a pragmatic setup, with two jobs in one workflow.

  • tfsec runs quickly on raw code.
  • checkov generates a Terraform plan and scans it for deeper context.

Create .github/workflows/iac-security.yml:

name: IaC Security (tfsec + Checkov)

on:
  pull_request:
  push:
    branches: [ main ]

permissions:
  contents: read
  security-events: write   # for SARIF uploads

jobs:
  tfsec:
    name: tfsec (code scan)
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.9.5

      - name: Run tfsec
        uses: aquasecurity/tfsec-action@v1
        with:
          working_directory: .
          additional_args: >
            --concise-output
            --format sarif
            --out tfsec.sarif
            --soft-fail-severities LOW, MEDIUM

      - name: Upload SARIF (tfsec)
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: tfsec.sarif

  checkov:
    name: Checkov (plan scan)
    runs-on: ubuntu-latest
    needs: tfsec
    env:
      TF_IN_AUTOMATION: "true"
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.9.5

      - name: Terraform Init
        run: terraform init -input=false

      - name: Terraform Plan (json)
        run: terraform plan -out=tfplan.bin && terraform show -json tfplan.bin > tfplan.json

      - name: Run Checkov on plan
        uses: bridgecrewio/checkov-action@v12
        with:
          directory: .
          file: tfplan.json
          framework: terraform_plan
          soft_fail: false
          output_format: sarif
          output_file_path: checkov.sarif

      - name: Upload SARIF (Checkov)
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: checkov.sarif

Why this layout works

  • Fail fast on code issues with tfsec: developers get immediate, actionable feedback. Aqua Security maintains the tfsec Action and supports generating SARIF for GitHub's code scanning UI.
  • Plan-aware checks catch issues that only appear once variables and modules resolve during plan. Checkov documents Terraform Plan Scanning explicitly, and you can feed it the terraform show -json output (as in the job above)
  • SARIF is the standard format GitHub consumes for third-party scanners; the upload-sarif Action publishes results to the Security tab.

If you manage Terraform via Terrateam, you can move the heavy lifting out of bespoke CI and let Terrateam's workflow engine run Checkov against the plan automatically via a dedicated step. Read our guide to "Scanning Terraform Plans with Checkov" and the workflows reference.

Interpreting results and customizing rules

Security scanners produce a lot of signal; the trick is turning that into a clean, enforceable bar that your team can actually meet.

1. Start with severity gates

  • In tfsec, use --format sarif + --out to produce a SARIF file and consider soft-failing lower severities while you roll out. You'll still block HIGH/CRITICAL.
  • In Checkov, you can choose outputs (CLI, JSON, JUnit, SARIF) and control failure behavior; for example, it documents soft-fail options.

Starting with severity gates helps you "land" scanners without blocking every PR on day one.

2. Triage findings by class

Focus on classes with the highest blast radius:

  • Network exposure – unrestricted ingress/egress, public endpoints without controls.
  • Data at rest – unencrypted storage (EBS, S3, RDS).
  • Identity – wildcard IAM policies, admin-level roles bound to services.
  • Secrets – inline credentials or missing secret managers.

You'll see these patterns repeatedly, so create playbooks to standardize fixes.

3. Use inline, documented exceptions

Sometimes the scanner is right, but the business requirement is stronger. In those rare cases, annotate with a targeted skip and a reason in code comments (both tools support this pattern), e.g.,

  • #tfsec:ignore: AWS002 – allow public read on this demo bucket
  • #checkov:skip=CKV_AWS_20 – legacy ingress for partner IP

Checkov's docs cover suppression and custom policy approaches.

4. Implement plan-level validation for derived values

A lot of "is this actually public?" questions depend on variables, data sources, or module defaults. Running Checkov on the plan answers these with real resolved values. That's why many teams run both: quick code-only checks plus plan-aware checks. Terrateam's workflow step makes this natural to adopt in your GitOps process.

5. Centralize org standards as policy

If you already enforce policy with OPA/Conftest, keep using it alongside scanners. Terrateam integrates with OPA, allowing you to encode "musts" (e.g., "S3 buckets must use KMS keys") as code, and the system applies or requires approvals based on policy outcomes.

Extending and tuning rules

Both tools provide substantial built-in checks; still, you'll want lightweight customization.

  • Directory or module scoping: Point the scanner only at changed paths or critical modules to reduce noise and runtime.
  • Rule selection: Enforce HIGH/CRITICAL globally, then ratchet to MEDIUM as debt burns down. Checkov lets you tune failure behavior (e.g., soft-fail by severity).
  • Baselining: Use a baseline to block regressions while you remediate over time if your repo has existing debt.
  • Output formats: Emit SARIF to surface results natively in GitHub's "Security" tab; JSON outputs help you build dashboards. (Both tools support SARIF).

For org-wide policy, consider "policy after plan": run a plan, evaluate with Checkov and/or OPA, require approvals, and only then apply.

The benefits of integrating Checkov and tfsec into your Terraform Workflow

  1. Catch issues before they become outages: Blocking merges on CRITICAL/HIGH findings prevents you from having to work late on Fridays.
  2. Developer-centric feedback: Inline SARIF comments and concise console output keep the loop short – developers don't need to leave the PR to see what failed. GitHub treats uploaded SARIF as first-class code scanning results.
  3. Consistent security bar across repos: A shared workflow enforces the same checks for all Terraform modules and services.
  4. Plan-aware accuracy: Scanning plan output reduces false positives related to variable resolution and merges, a capability Checkov documents and Terrateam integrates natively.
  5. Policy as code alignment: Pair scanners with OPA/Conftest to encode non-negotiables, approvals, and environment-specific rules.
  6. Fits GitOps orchestration: Terrateam treats planning, policy, and scanning as first-class workflow steps, ensuring that reviews and approvals happen with full context in the pull request. See the workflows reference and gate/approval capabilities.

Best Practices Checklist

  • Run on every PR and push to main. Use the tfsec and Checkov Actions.
  • Fail on HIGH/CRITICAL to start, then tighten the settings. Use soft-fail options while you roll out.
  • Scan the Terraform plan for deeper context.
  • Use SARIF uploads for rich PR annotations.
  • Document exceptions with reasons and reviews. Checkov supports suppression; tfsec supports ignore comments.
  • Add OPA/Conftest for org-specific policies.
  • Automate with Terrateam where possible to simplify CI and centralize workflows.

Example: Moving scanning into Terrateam workflows

If you're already using Terrateam, you can simplify your CI by declaring checks in Terrateam's config and letting it orchestrate plan → scan → policy → approval:

# .terrateam/config.yml (excerpt)
workflows:
  - tag_query: "dir:production"
    plan:
      - type: init
      - type: plan
      - type: checkov
      - type: conftest

This approach centralizes security while keeping the pull request as the source of truth.

Checkov vs. tfsec vs. Terrascan

You'll often see comparisons of Checkov vs. tfsec vs terrascan. In practice, most teams don't pick one forever, they compose tools. However, each does have its pros and cons:

ToolProsConsWhen to use it
CheckovBroad IaC support with extensive built-in policies and strong integrationsSlightly slower and more complex to configureBest for multi-framework environments needing wide compliance coverage
tfsecFast, lightweight, and easy to integrate for Terraform use casesLimited to Terraform with minimal customizationIdeal for Terraform-only projects that require quick security checks
TerrascanMulti-IaC support with powerful OPA-based custom policy controlSlightly steeper learning curve and fewer default policiesSuited for enterprises needing strict, customizable compliance across IaC types

You should also consider additional tools (e.g., OPA/Conftest) for organization-specific guardrails and approvals.

Conclusion

Security scans shouldn't be a once-a-quarter audit, they should be a daily guardrail that runs every time someone proposes a change. By combining tfsec and Checkov in GitHub Actions, you block risky Terraform changes before they land. By scanning plans, you catch issues that only show up once everything is resolved.

Suppose you'd like less DIY CI and more orchestrated GitOps, with plans, scans, policies, and approvals baked into the pull request. If that's the case,give Terrateam a try. It integrates tightly with Checkov for plan scanning and with OPA for policy enforcement, allowing you to ship safer infrastructure with confidence. Sign up here: https://terrateam.io/signup.