Skip to main content
RunBook Academy

Git, CI/CD & GitOpsL · Terraform CISecurityScanning

tfsec and checkov — what security scanners actually check, and the false positive rate

Intermediate⏱ ~28 mingitterraform

What you'll learn

  • Run tfsec . against a Terraform configuration and interpret the output
  • Run checkov -d . --framework terraform and interpret the output
  • Identify the categories of finding each tool produces and where they overlap
  • Triage false positives and recognise why security scanners are necessary and not sufficient

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

terraform fmt checks whitespace. terraform validate checks HCL syntax. tflint checks team policy and provider deprecations. None of the three checks whether the configuration is safe to run. The security-scanning layer - tfsec and checkov - exists to fill exactly that gap. It scans the configuration for patterns that match known misconfigurations: open security groups, unencrypted storage, missing audit logs, public S3 buckets, IAM policies with administrative scope. The two tools overlap heavily and have complementary strengths; running both, or running one and using the other for triage, is the production pattern.

What tfsec actually checks

tfsec reads the Terraform source and matches it against a built-in ruleset maintained by Aqua Security. The rules cover AWS, Azure, GCP, and Kubernetes providers. Categories:

  • Public exposure - security groups, S3 bucket policies, Azure storage accounts, GCP storage buckets that allow unauthenticated access.
  • Encryption - storage that is not encrypted at rest, databases that do not enforce TLS.
  • Logging and monitoring - CloudTrail, Azure Activity Log, GCP Audit Logs that are not enabled; resources without required tags for compliance.
  • IAM - policies that grant *:*, role assumption without external IDs, resource policies that allow anonymous access.

The command that belongs in CI:

tfsec .

The . is the directory to scan. Output is human-readable by default; --format json produces a parseable report. --soft-fail makes tfsec exit zero even when findings are present, which is useful while a baseline is being established.

The command that belongs in CI:

tfsec .

The . is the directory to scan; . means the working tree. Output is human-readable by default; --format json produces a parseable report. --soft-fail makes tfsec exit zero even when findings are present, which is useful while a baseline is being established.

flowchart LR
    A[HCL source] --> B[tfsec .]
    A --> C[checkov -d .]
    B --> D[Findings: severity, resource, rule]
    C --> E[Findings: check_id, resource, guideline]
    D --> F[Triage]
    E --> F
    F --> G{Real?}
    G -->|yes| H[Fix in PR]
    G -->|no| I[Suppress with justification]

What checkov actually checks

checkov scans the same patterns from a different rule corpus. Where tfsec maintains its own rules, checkov pulls in rules from a broader community and from cloud-provider best-practice guides. The two tools frequently agree on a finding; they sometimes diverge (checkov has more rules for Kubernetes manifests, tfsec has more rules for IAM policy structure).

The command that belongs in CI:

checkov -d . --framework terraform
  • -d . scans the current directory recursively.
  • --framework terraform restricts the scan to Terraform; checkov also scans Dockerfile and Kubernetes manifests, and the framework flag keeps the run focused.

Output includes a per-check summary (passed/failed/skipped counts), a per-resource finding list, and a final compliance score. The two tools overlap heavily and have complementary strengths; running both, or running one and using the other for triage, is the production pattern.

The false positive rate

Neither tool has access to runtime reality. Both work from the configuration alone, which means:

  • An aws_security_group rule that opens port 22 to 0.0.0.0/0 is flagged even when paired with an aws_security_group_rule that removes that ingress later.
  • A bucket that is “public” in the policy is flagged even when it sits behind a CloudFront origin access identity.
  • An IAM policy with Action: "*" is flagged even when it is condition-scoped to a specific IP range.

These are not bugs; they are the cost of static analysis against a language that supports late-binding semantics. The production response is a documented suppression process: each suppression cites a finding ID and a written justification; suppressions are committed so they are reviewable; and they are reviewed periodically, since a suppression that was valid last quarter may not be valid after a provider upgrade.

Necessary and not sufficient

A passing security scan is not a security guarantee. The scan reads the configuration; security depends on the configuration, the credentials, the state file, the pipeline, and the operator’s actions. A configuration that passes tfsec and checkov can still be unsafe if the credentials are over-scoped, if the state file is not encrypted at rest, if the pipeline is itself compromised, or if the change is applied by an operator who bypasses the pipeline.

Security scanning is one layer in a stack that includes IAM scope, state encryption, pipeline hardening, and operator discipline. It catches the most common category of mistake - “I configured this resource in a way that is unsafe by default” - and it does that job well. It is not, and cannot be, the only layer.

Production discipline

  1. Security scans run on every PR, not on a schedule. A weekly scan catches nothing that a per-PR scan would have caught.
  2. Findings fail the build above a severity threshold. CRITICAL and HIGH block merge; MEDIUM and LOW warn.
  3. Suppressions are committed, justified, and reviewed. A suppression without a justification is a comment that will be read as “ignore this” forever.
  4. Scanner versions are pinned. A scanner that auto-updates is a moving target on a supply-chain-critical layer.

Cross-course references

  • Terraform for Production Sysadmins - Part XVIII (SecurityPatterns) covers the cloud-side controls the scanners verify.
  • This course, Part XLIX (InfrastructureCI) - lesson git-cicd-gitops-xlix-04-security-scanning is the general framing of the security-scanning stage; this lesson is the Terraform-specific instantiation.
  • Linux for Production Sysadmins - Part XXVI (AuditLogging) covers CloudTrail and its analogues, which the scanners check for presence.

Quiz

Knowledge check · 4 questions

  1. Q1. A tfsec scan reports an aws_s3_bucket as public. The bucket sits behind a CloudFront origin access identity and is unreachable from the public internet. What is the right response?

  2. Q2. A Terraform configuration that passes tfsec and checkov is secure.

  3. Q3. Name three categories of finding tfsec produces, and explain why a finding in any of them can be a false positive.

  4. Q4. Diagnose why a security-scan gate was ignored, and propose a fix that keeps the gate meaningful.

    A team enables tfsec in CI. The first run produces 47 findings across the existing codebase, most of them pre-existing technical debt. The team silences them all with `# tfsec:ignore` comments to unblock the PR pipeline. Within a quarter, a developer adds a new resource with a genuinely dangerous misconfiguration. The misconfiguration reaches production because the suppression comment is treated as a precedent.

Passing score: 75%. Answers are checked in this browser.